Skip to content

Conversation

@dominikvozr
Copy link

@dominikvozr dominikvozr commented Sep 2, 2025

Description

This PR introduces a GenericMapCache implementation of Spring’s Cache interface, backed by in-memory Maps. The solution replaces ad-hoc local maps with proper cache wrappers that integrate with Spring Boot Actuator’s /caches endpoints.

This makes all cache operations consistent and manageable via Actuator.

Implements NAE-2182

Blocking PRs

This PR depends on NAE-2212/PR #363

How Has Been This Tested?

The functionality was tested using Postman to verify cache operations through the Actuator endpoints.

Name Tested on
OS Windows 11
Runtime Java 21
Dependency Manager Apache Maven 3.9.9
Framework version Spring Boot 3.2.5, NAE 7.0.0-rev7
Run parameters mvn clean install
Other configuration

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • My changes have been checked, personally or remotely, with @...
  • I have commented my code, particularly in hard-to-understand areas
  • I have resolved all conflicts with the target branch of the PR
  • I have updated and synced my code with the target branch
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing tests pass locally with my changes:
    • Lint test
    • Unit tests
    • Integration tests
  • I have checked my contribution with code analysis tools:
  • I have made corresponding changes to the documentation:
    • Developer documentation
    • User Guides
    • Migration Guides

Summary by CodeRabbit

  • New Features

    • Bounded, size-limited caches for actions and functions with on-demand reload and global function cache access.
    • Validator to detect and migrate deprecated function scope attributes.
    • New endpoints/config to expose additional actuator info.
  • Refactor

    • Centralized cache manager and unified caching across services.
    • Adopted explicit default vs. latest process version semantics.
  • Bug Fixes

    • Added null checks and clearer errors; improved version activation on import/delete.
  • Tests

    • Added versioned test resources and updated tests.
  • Documentation

    • New functions guide added.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Sep 2, 2025

Walkthrough

Adds bounded generic caches and a global functions cache, introduces default/latest PetriNet version semantics with import/version lifecycle handling, updates callers/tests from "newest" to "default/latest", adds function validation, and exposes actuator endpoints.

Changes

Cohort / File(s) Summary
Cache infra & types
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java, application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java, application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java, application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java
Replace CachingConfigurerSupport with CachingConfigurer; build SimpleCacheManager with per-name ConcurrentMapCache; add bounded GenericMapCache for actions and functions; add CacheMapKeys and new cache-name properties (petriNetDefault, petriNetLatest, globalFunctions) and page-size property.
Field actions caching
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java, .../interfaces/IFieldActionsCacheService.java
Move from namespace-scoped to a global functions cache; add reloadCachedFunctions(String), cacheAllPetriNetFunctions(), getGlobalFunctionsCache(), clearGlobalFunctionCache(); use synchronized bounded maps and lazy IPetriNetService injection.
PetriNet versioning & persistence
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java, application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java, application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/repositories/PetriNetRepository.java, nae-object-library/src/main/java/.../PetriNet.java, nae-object-library/src/main/java/.../ElasticPetriNet.java, nae-object-library/src/main/java/.../version/Version.java
Introduce default/latest semantics: add getDefaultVersionByIdentifier and getLatestVersionByIdentifier; add defaultVersion flag and makeDefault/makeNonDefault on PetriNet; repo methods for default flag; add doSaveInternal and checkAndHandleProcessVersion to manage import/version lifecycle and transactional imports.
Call-site updates (newest → default/latest)
application-engine/src/main/java/**, application-engine/src/main/groovy/**, application-engine/src/test/**, application-engine/src/test/resources/**
Replace calls from "newest" to "default/latest" APIs across multiple callers, add null-checks and ResourceNotFoundException where applicable, update tests and fixtures to default-version semantics.
Function import & validation
application-engine/src/main/java/com/netgrif/application/engine/importer/service/FunctionFactory.java, application-engine/src/main/java/com/netgrif/application/engine/importer/service/FunctionValidator.java, application-engine/src/main/java/com/netgrif/application/engine/importer/service/IFunctionValidator.java, nae-object-library/src/main/java/.../FunctionScope.java
Add IFunctionValidator + FunctionValidator to normalize deprecated NAMESPACE scope to GLOBAL; inject validator into FunctionFactory; adjust FunctionScope constants and docs.
Runner config & tuning
application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java
Rename namespaceCacheSizeglobalFunctionsCacheSize and update Javadoc; expose function caching page-size property.
Configuration & build
application-engine/src/main/resources/application.yaml, application-engine/pom.xml
Expose actuator endpoints (include: "*", keep exclude: shutdown); remove explicit JUnit 5 API/engine dependencies (keep umbrella).
Tests, fixtures & resources
application-engine/src/test/**, application-engine/src/test/resources/petriNets/*
Update tests to default-version APIs and messages; add PetriNet version fixtures (process_version_*.xml); adjust test helpers to clear global function cache.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Caller
  participant FieldActionsCacheService as FACS
  participant CacheManager
  participant ActionsCache as "Cache(actions)"
  participant PetriNetService as PNS

  Caller->>FACS: request compiled action (fieldId, actionRef)
  FACS->>CacheManager: getCache("actions")
  CacheManager-->>FACS: ActionsCache
  FACS->>ActionsCache: get(key, loader)
  alt cache hit
    ActionsCache-->>FACS: compiledClosure
  else cache miss
    FACS->>PNS: getDefaultVersionByIdentifier(petriNetId)
    PNS-->>FACS: PetriNet
    FACS->>FACS: compile/rehydrate closure
    FACS->>ActionsCache: put(key, compiledClosure)
  end
  FACS-->>Caller: return Closure
Loading
sequenceDiagram
  autonumber
  participant Caller
  participant FACS
  participant CacheManager
  participant GlobalFuncs as "Cache(globalFunctions)"
  participant PNS

  Caller->>FACS: reloadCachedFunctions(petriNetId)
  FACS->>CacheManager: getCache("globalFunctions")
  CacheManager-->>FACS: GlobalFuncs
  FACS->>GlobalFuncs: evict(petriNetId)
  FACS->>PNS: getDefaultVersionByIdentifier(petriNetId)
  PNS-->>FACS: PetriNet (or null)
  alt PetriNet found
    FACS->>FACS: evaluate/compile functions
    FACS->>GlobalFuncs: put(petriNetId, evaluatedFunctions)
  else not found
    FACS-->>Caller: handled (no-op)
  end
  FACS-->>Caller: void
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'NAE-2182 Clearing actions cache' refers to a narrow aspect of the changes (clearing/managing cache), but the changeset encompasses much broader refactoring including cache infrastructure redesign, versioning semantics changes (newest → default), PetriNet function scope changes (namespace → global), and API restructuring across multiple services. While cache clearing is involved, it is not the primary or most important change. Revise the title to reflect the main architectural changes, such as 'NAE-2182 Refactor caching infrastructure with default version semantics' or 'NAE-2182 Restructure PetriNet versioning and cache management', to accurately convey the scope and intent of this substantial refactoring.
Docstring Coverage ⚠️ Warning Docstring coverage is 22.34% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (6)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java (1)

3-7: Make utility holder non-instantiable and final.

Prevents accidental instantiation/extension and clarifies intent.

-public class CacheMapKeys {
+public final class CacheMapKeys {
     public static final String ACTIONS = "actionsCache";
     public static final String FUNCTIONS = "functionsCache";
     public static final String NAMESPACE_FUNCTIONS = "namespaceFunctionsCache";
+
+    private CacheMapKeys() {
+        throw new AssertionError("No instances");
+    }
 }
application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)

22-24: Leaking the mutable Map via getNativeCache couples callers to internals.

FieldActionsCacheService manipulating the map directly bypasses cache invariants/locking.

Prefer cache APIs (get/put/evict/clear). If direct access is required, document it and ensure all such usage is confined and synchronized.

application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java (1)

43-65: Confirm thread-safety of MaxSizeHashMap or wrap it.

If MaxSizeHashMap is not concurrent, concurrent access through the cache will race.

Options:

  • Wrap: () -> java.util.Collections.synchronizedMap(new MaxSizeHashMap<>(...))
  • Or rely on the synchronized GenericMapCache changes above.
  • Add a brief Javadoc on MaxSizeHashMap usage here clarifying concurrency expectations.
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (3)

142-143: Avoid raw cast in getNamespaceFunctionCache; ensure type-safety and null-safety.

Keeps method contract explicit and prevents CCE if the impl changes.

-        return new HashMap<>((Map) cacheManager.getCache(CacheMapKeys.NAMESPACE_FUNCTIONS).getNativeCache());
+        Map<String, List<CachedFunction>> nativeMap =
+                (Map<String, List<CachedFunction>>) getRequiredCache(CacheMapKeys.NAMESPACE_FUNCTIONS).getNativeCache();
+        return new HashMap<>(nativeMap);

147-148: Clear methods: use guarded cache retrieval.

-        cacheManager.getCache(CacheMapKeys.ACTIONS).clear();
+        getRequiredCache(CacheMapKeys.ACTIONS).clear();
@@
-        cacheManager.getCache(CacheMapKeys.NAMESPACE_FUNCTIONS).clear();
+        getRequiredCache(CacheMapKeys.NAMESPACE_FUNCTIONS).clear();
@@
-        cacheManager.getCache(CacheMapKeys.FUNCTIONS).clear();
+        getRequiredCache(CacheMapKeys.FUNCTIONS).clear();

Also applies to: 151-153, 156-158


74-84: Keep this service cache-implementation-agnostic; avoid getNativeCache() and instanceof Map.

Relying on the native Map couples you to GenericMapCache and its thread-safety characteristics. Using Cache APIs (get(key, Callable), evict, clear) preserves portability and simplifies reasoning.

Also applies to: 89-99, 142-143

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b8abb83 and e917f76.

📒 Files selected for processing (5)
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java (1 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java (1 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (4 hunks)
  • application-engine/src/main/resources/application.yaml (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java (1)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java (1)
  • CacheMapKeys (3-7)
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (2)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java (1)
  • CacheMapKeys (3-7)
application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)
  • Slf4j (8-78)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: Build
  • GitHub Check: task-list-completed
🔇 Additional comments (3)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java (2)

67-70: CacheManager wiring LGTM.

Unified SimpleCacheManager with default and specialized caches is clean and extensible.


37-41: Exclude specialized cache names from default caches. Filter out CacheMapKeys.ACTIONS, CacheMapKeys.FUNCTIONS and CacheMapKeys.NAMESPACE_FUNCTIONS from the properties.getAllCaches() stream before mapping to ConcurrentMapCache to avoid registering those caches twice.

application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (1)

28-37: Verify FieldActionsCacheService bean registration
Confirm that FieldActionsCacheService is annotated (e.g. @service or @component) or declared via a @bean method so Spring will pick up the new constructor signature, and search your configuration for any @bean methods or manual instantiations of FieldActionsCacheService using the old two-arg constructor.

Comment on lines 89 to 99
Cache functionsCache = cacheManager.getCache(CacheMapKeys.FUNCTIONS);
Object nativeFunctionsCache = functionsCache.getNativeCache();

if (nativeFunctionsCache instanceof Map<?, ?> map) {
functions.forEach(function -> {
if (!map.containsKey(function.getStringId())) {
functionsCache.put(function.getStringId(), CachedFunction.build(shell, function));
}
cachedFunctions.add((CachedFunction) functionsCache.get(function.getStringId()).get());
});
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid native Map access in getCachedFunctions; compute-if-absent via Cache API.

Works with any Cache impl and removes silent empty-return if native cache isn’t a Map.

-        Cache functionsCache = cacheManager.getCache(CacheMapKeys.FUNCTIONS);
-        Object nativeFunctionsCache = functionsCache.getNativeCache();
-
-        if (nativeFunctionsCache instanceof Map<?, ?> map) {
-            functions.forEach(function -> {
-                if (!map.containsKey(function.getStringId())) {
-                    functionsCache.put(function.getStringId(), CachedFunction.build(shell, function));
-                }
-                cachedFunctions.add((CachedFunction) functionsCache.get(function.getStringId()).get());
-            });
-        }
+        Cache functionsCache = getRequiredCache(CacheMapKeys.FUNCTIONS);
+        for (Function function : functions) {
+            CachedFunction cf = functionsCache.get(function.getStringId(),
+                    () -> CachedFunction.build(shell, function));
+            cachedFunctions.add(cf);
+        }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 119 to 121
exclude: shutdown
include: "*"
endpoint:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Actuator exposure is fully open; combined with anonymous '/manage/' this is a security risk.**

include: "*" exposes sensitive endpoints (env, beans, heapdump, caches). Your config also lists /manage/** under anonymous exceptions (Lines 87–88), making these endpoints publicly accessible.

Apply both changes:

-      anonymous-exceptions: /api/auth/signup,/api/auth/token/verify,/api/auth/reset,/api/auth/recover,/manage/**
+      anonymous-exceptions: /api/auth/signup,/api/auth/token/verify,/api/auth/reset,/api/auth/recover
           exposure:
             exclude: shutdown
-            include: "*"
+            include: health,info,metrics,prometheus,caches

Optional hardening:

  • Require auth/role for /manage/** or move management to a separate port.
  • Only include the endpoints you actually need in non-dev profiles.
🤖 Prompt for AI Agents
In application-engine/src/main/resources/application.yaml around lines 119–121
(and note anonymous /manage/** at lines 87–88), the actuator exposure is set to
include: "*" which together with anonymous access to /manage/** exposes
sensitive endpoints; change the actuator exposure to list only the required
endpoints (e.g., health, info, metrics) instead of "*" and remove or restrict
the anonymous exception for /manage/**—either require authentication/appropriate
role for /manage/** or move management endpoints to a separate management port;
make these config edits in the relevant profile(s) and/or add security
constraints so management endpoints are not publicly accessible.

@coderabbitai coderabbitai bot added improvement A change that improves on an existing feature Large labels Sep 3, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

♻️ Duplicate comments (5)
application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)

15-15: Make map-backed cache thread-safe; compute-and-store must be atomic.

Backed by a plain Map with unsynchronized mutations; races with put/evict/clear and get(loader). Clear also reassigns the map mid-flight. Synchronize mutations and use double-checked locking in get(loader).

 public abstract class GenericMapCache<V> implements Cache {
@@
-    protected volatile Map<String, V> map;
+    protected volatile Map<String, V> map;
+    private final Object monitor = new Object();
@@
-    @Override
-    public <T> T get(Object key, java.util.concurrent.Callable<T> loader) {
+    @Override
+    public <T> T get(Object key, java.util.concurrent.Callable<T> loader) {
         String stringKey = String.valueOf(key);
-        Object present = map.get(stringKey);
+        Object present = map.get(stringKey);
 
         if (present != null) {
             return (T) present;
         }
 
-        try {
-            T computed = loader.call();
-            if (computed != null) {
-                map.put(stringKey, safeCast(computed));
-            }
-            return computed;
-        } catch (Exception ex) {
-            throw new org.springframework.cache.Cache.ValueRetrievalException(stringKey, loader, ex);
-        }
+        synchronized (monitor) {
+            present = map.get(stringKey);
+            if (present != null) {
+                return (T) present;
+            }
+            try {
+                T computed = loader.call();
+                if (computed != null) {
+                    map.put(stringKey, safeCast(computed));
+                }
+                return computed;
+            } catch (Exception ex) {
+                throw new org.springframework.cache.Cache.ValueRetrievalException(stringKey, loader, ex);
+            }
+        }
     }
@@
-    public void put(Object key, Object value) {
-        map.put(String.valueOf(key), safeCast(value));
+    public void put(Object key, Object value) {
+        synchronized (monitor) {
+            map.put(String.valueOf(key), safeCast(value));
+        }
     }
@@
-    public void evict(Object key) {
-        map.remove(String.valueOf(key));
+    public void evict(Object key) {
+        synchronized (monitor) {
+            map.remove(String.valueOf(key));
+        }
     }
@@
-    public void clear() {
-        this.map = mapFactory.get();
+    public void clear() {
+        synchronized (monitor) {
+            this.map = mapFactory.get();
+        }
         log.info("{} cache cleared", this.getName());
     }

Also applies to: 33-51, 53-67

application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (4)

55-62: Fail fast on missing cache; avoid NPE.

-        Cache namespaceFunctionsCache = cacheManager.getCache(CacheMapKeys.NAMESPACE_FUNCTIONS);
+        Cache namespaceFunctionsCache = getRequiredCache(CacheMapKeys.NAMESPACE_FUNCTIONS);
@@
-            namespaceFunctionsCache.evictIfPresent(petriNet.getIdentifier());
+            namespaceFunctionsCache.evictIfPresent(petriNet.getIdentifier());

Support code (add once in this class):

private Cache getRequiredCache(String name) {
    Cache cache = cacheManager.getCache(name);
    if (cache == null) throw new IllegalStateException("Cache '" + name + "' not configured");
    return cache;
}

65-69: Guard cache retrieval and reload atomically.

-    public void reloadCachedFunctions(String petriNetId) {
-        cacheManager.getCache(CacheMapKeys.NAMESPACE_FUNCTIONS).evictIfPresent(petriNetId);
-        cachePetriNetFunctions(petriNetService.getNewestVersionByIdentifier(petriNetId));
-    }
+    public void reloadCachedFunctions(String petriNetId) {
+        getRequiredCache(CacheMapKeys.NAMESPACE_FUNCTIONS).evictIfPresent(petriNetId);
+        cachePetriNetFunctions(petriNetService.getNewestVersionByIdentifier(petriNetId));
+    }

79-89: Use Cache APIs; avoid peeking into native Map and eliminate TOCTOU.

-        Cache actionsCache = cacheManager.getCache(CacheMapKeys.ACTIONS);
-        Object nativeActionsCache = actionsCache.getNativeCache();
-
-        if (nativeActionsCache instanceof Map<?, ?> map) {
-            if (shouldRewriteCachedActions || !map.containsKey(stringId) ) {
-                Closure code = (Closure) shell.evaluate("{-> " + action.getDefinition() + "}");
-                actionsCache.put(stringId, code);
-            }
-        }
-        return (Closure) actionsCache.get(stringId).get();
+        Cache actionsCache = getRequiredCache(CacheMapKeys.ACTIONS);
+        if (shouldRewriteCachedActions) {
+            actionsCache.evict(stringId);
+        }
+        return actionsCache.get(stringId,
+                () -> (Closure) shell.evaluate("{-> " + action.getDefinition() + "}"));

93-105: Compute functions atomically and avoid native cache access.

-        Cache functionsCache = cacheManager.getCache(CacheMapKeys.FUNCTIONS);
-        Object nativeFunctionsCache = functionsCache.getNativeCache();
-
-        if (nativeFunctionsCache instanceof Map<?, ?> map) {
-            functions.forEach(function -> {
-                if (!map.containsKey(function.getStringId())) {
-                    functionsCache.put(function.getStringId(), CachedFunction.build(shell, function));
-                }
-                cachedFunctions.add((CachedFunction) functionsCache.get(function.getStringId()).get());
-            });
-        }
+        Cache functionsCache = getRequiredCache(CacheMapKeys.FUNCTIONS);
+        for (Function function : functions) {
+            CachedFunction cf = functionsCache.get(function.getStringId(),
+                    () -> CachedFunction.build(shell, function));
+            cachedFunctions.add(cf);
+        }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e917f76 and bd719b5.

📒 Files selected for processing (9)
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java (1 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java (1 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsMapCache.java (1 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java (1 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (3 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java (2 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (4 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IFieldActionsCacheService.java (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-31T23:40:46.499Z
Learnt from: tuplle
PR: netgrif/application-engine#334
File: application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java:204-214
Timestamp: 2025-07-31T23:40:46.499Z
Learning: In the PetriNetService.importPetriNet method, existingNet.getVersion() cannot be null because all existing nets in the system were deployed through processes that ensure every net always has a version assigned.

Applied to files:

  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java
🧬 Code graph analysis (3)
application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java (2)
application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)
  • Slf4j (10-80)
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (1)
  • Slf4j (66-581)
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (4)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java (1)
  • CacheMapKeys (3-7)
application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java (1)
  • Slf4j (10-41)
application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)
  • Slf4j (10-80)
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (1)
  • Slf4j (66-581)
application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)
application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java (1)
  • Slf4j (10-41)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Test
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
🔇 Additional comments (6)
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java (1)

4-4: No action on wildcard import.

Acceptable here unless the project enforces “no wildcard imports”.

application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (2)

8-8: No action on wildcard import.

Safe refactor.


558-569: Add index on functions._id in petriNet collection
The query in PetriNetService.findByFunctionId filters on functions._id; without an index this will trigger a full collection scan. Verify (via @indexed on Function.objectId, a database migration, or manual index setup) that an index exists for functions._id.

application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java (1)

15-17: Ignore namespace vs PetriNet ID key concern

All cache operations on NAMESPACE_FUNCTIONS use petriNet.getIdentifier() as the key (see FieldActionsCacheService.java:55 and 67), so the cache is intentionally keyed by PetriNet ID. No changes required.

Likely an incorrect or invalid review comment.

application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java (1)

13-13: Confirm reload key semantics

reloadCachedFunctions(stringKey) presumes stringKey is the Petri net identifier the service expects. Please confirm the key format aligns with IFieldActionsCacheService’s contract; otherwise, misses will never populate.

I can generate a short repo scan to locate all reloadCachedFunctions(...) overloads and usages to confirm the expected key shape. Do you want that?

application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java (1)

46-50: Filtering specialized cache names is unnecessary CacheConfigurationProperties.getAllCaches() returns only the default caches (petriNetById, petriNetByIdentifier, petriNetNewest, petriNetCache, loadedModules) and any user-added names—it never includes CacheMapKeys.ACTIONS, CacheMapKeys.FUNCTIONS, or CacheMapKeys.NAMESPACE_FUNCTIONS, so no duplicate registrations can occur by default.

Likely an incorrect or invalid review comment.

Comment on lines 17 to 27
@Override
public ValueWrapper get(Object key) {
String stringKey = String.valueOf(key);

Object valueObject = map.get(stringKey);
if (valueObject != null) {
return new org.springframework.cache.support.SimpleValueWrapper(valueObject);
}
fieldActionsCacheService.reloadCachedFunctions(stringKey);
return new org.springframework.cache.support.SimpleValueWrapper(map.get(stringKey));
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Cache.get(Object) should return null on miss; don’t wrap null

Returning SimpleValueWrapper(null) violates the Cache contract. Return null when the key is absent or the reload doesn’t populate the key.

     @Override
     public ValueWrapper get(Object key) {
         String stringKey = String.valueOf(key);
 
         Object valueObject = map.get(stringKey);
         if (valueObject != null) {
             return new org.springframework.cache.support.SimpleValueWrapper(valueObject);
         }
         fieldActionsCacheService.reloadCachedFunctions(stringKey);
-        return new org.springframework.cache.support.SimpleValueWrapper(map.get(stringKey));
+        Object refreshed = map.get(stringKey);
+        return refreshed != null
+                ? new org.springframework.cache.support.SimpleValueWrapper(refreshed)
+                : null;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Override
public ValueWrapper get(Object key) {
String stringKey = String.valueOf(key);
Object valueObject = map.get(stringKey);
if (valueObject != null) {
return new org.springframework.cache.support.SimpleValueWrapper(valueObject);
}
fieldActionsCacheService.reloadCachedFunctions(stringKey);
return new org.springframework.cache.support.SimpleValueWrapper(map.get(stringKey));
}
@Override
public ValueWrapper get(Object key) {
String stringKey = String.valueOf(key);
Object valueObject = map.get(stringKey);
if (valueObject != null) {
return new org.springframework.cache.support.SimpleValueWrapper(valueObject);
}
fieldActionsCacheService.reloadCachedFunctions(stringKey);
Object refreshed = map.get(stringKey);
return refreshed != null
? new org.springframework.cache.support.SimpleValueWrapper(refreshed)
: null;
}
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java
around lines 17 to 27, the get(Object) implementation currently returns new
SimpleValueWrapper(null) on misses which violates the Cache contract; modify the
method to convert the key to string, try map.get(stringKey) and if non-null
return a SimpleValueWrapper, otherwise call
fieldActionsCacheService.reloadCachedFunctions(stringKey), then re-check
map.get(stringKey) and return a SimpleValueWrapper only if that second lookup is
non-null; if still null return null (do not wrap null).

Comment on lines 29 to 40
public <T> T get(Object key, Class<T> type) {
String stringKey = String.valueOf(key);
Object valueObject = map.get(stringKey);

if (valueObject != null) {
return type.cast(valueObject);
}

fieldActionsCacheService.reloadCachedFunctions(stringKey);
return type.cast(map.get(stringKey));

}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Override typed get; mirror null semantics after reload

Add @Override and return null when still missing.

-    public <T> T get(Object key, Class<T> type) {
+    @Override
+    public <T> T get(Object key, Class<T> type) {
         String stringKey = String.valueOf(key);
         Object valueObject = map.get(stringKey);
 
         if (valueObject != null) {
             return type.cast(valueObject);
         }
 
         fieldActionsCacheService.reloadCachedFunctions(stringKey);
-        return type.cast(map.get(stringKey));
+        Object refreshed = map.get(stringKey);
+        return refreshed != null ? type.cast(refreshed) : null;
 
     }
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java
around lines 29 to 40, add the @Override annotation to the get method and change
the logic so that after calling
fieldActionsCacheService.reloadCachedFunctions(stringKey) you read the map again
into a local variable, return null if that lookup is still missing, and only
call type.cast(...) when the looked-up value is non-null to preserve the
original null semantics and avoid a ClassCastException.

Comment on lines 61 to 69
Supplier<Map<String, CachedFunction>> functionsFactory = () -> new MaxSizeHashMap<>(fieldRunnerProperties.getFunctionsCacheSize());

caches.add(new FunctionsMapCache(
CacheMapKeys.FUNCTIONS,
functionsFactory,
fieldActionsCacheService,
petriNetService
));

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Align FUNCTIONS cache with stored type from FunctionsMapCache

With FunctionsMapCache storing Function, adjust the factory’s type accordingly.

-        Supplier<Map<String, CachedFunction>> functionsFactory = () -> new MaxSizeHashMap<>(fieldRunnerProperties.getFunctionsCacheSize());
+        Supplier<Map<String, Function>> functionsFactory = () -> new MaxSizeHashMap<>(fieldRunnerProperties.getFunctionsCacheSize());
@@
-        caches.add(new FunctionsMapCache(
+        caches.add(new FunctionsMapCache(
                 CacheMapKeys.FUNCTIONS,
                 functionsFactory,
                 fieldActionsCacheService,
                 petriNetService
         ));

Ensure you import com.netgrif.application.engine.objects.petrinet.domain.Function.

@@
-import com.netgrif.application.engine.workflow.domain.CachedFunction;
+import com.netgrif.application.engine.objects.petrinet.domain.Function;

Note: Keep the CachedFunction import for the namespace cache below.

🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java
around lines 61 to 69, the Supplier factory is typed as Supplier<Map<String,
CachedFunction>> but FunctionsMapCache stores Function instances; change the
factory type to Supplier<Map<String,
com.netgrif.application.engine.objects.petrinet.domain.Function>> (and update
the MaxSizeHashMap generic accordingly) and add an import for
com.netgrif.application.engine.objects.petrinet.domain.Function; keep the
existing CachedFunction import for the namespace cache below.

Comment on lines 337 to 345
/**
* Finds and returns a single function subdocument from the {@code petriNet} collection
* by its nested {@code functions._id}.
*
* @param functionId the string form of the function's ObjectId (24-hex)
* @return the matching {@code Function} subdocument, or {@code null} if not found
* @throws IllegalArgumentException if {@code functionId} is not a valid ObjectId
*/
Function findByFunctionId(String functionId);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Return nullability + index requirement for fast lookups by nested functions._id.

  • Mark the return as nullable to match the “null if not found” contract.
  • Add an index on petriNet.functions._id; without it, lookups will scan the whole collection at scale.

Apply near the signature:

-    Function findByFunctionId(String functionId);
+    @org.springframework.lang.Nullable
+    Function findByFunctionId(String functionId);

Create the index during startup (e.g., in configuration):

// Example (place in a @Configuration initializer)
mongoTemplate.indexOps("petriNet")
    .ensureIndex(new org.springframework.data.mongodb.core.index.Index()
        .on("functions._id", org.springframework.data.domain.Sort.Direction.ASC));

Would you like me to open a follow-up for the index migration?

🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java
around lines 337 to 345, the method signature and docs promise "null if not
found" but the return isn't marked nullable and there is no note about indexing
for performance; update the method signature to indicate the return can be null
(use your codebase's Nullable annotation or JSR305/@Nullable) and update the
Javadoc accordingly, and add a startup configuration that ensures an ascending
index on petriNet.functions._id (create the index via your
MongoTemplate/IndexOps during application startup) so nested lookups are
efficient at scale.

Comment on lines 558 to 569
@Override
public Function findByFunctionId(String functionId) {
Query query = new Query();

query.addCriteria(Criteria.where("functions._id").is(new ObjectId(functionId)));

PetriNet petriNet = mongoTemplate.findOne(query, PetriNet.class, "petriNet");

Optional<Function> optionalFunction = petriNet.getFunctions().stream().filter(function -> function.getObjectId().toString().equals(functionId)).findFirst();

return optionalFunction.orElse(null);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Fix NPE on miss and validate ObjectId; project only the matched function.

Current code NPEs when no PetriNet matches and accepts invalid ids. Also loads all functions; use elemMatch to project the matched one only.

Apply:

@@
     @Override
     public Function findByFunctionId(String functionId) {
-        Query query = new Query();
-
-        query.addCriteria(Criteria.where("functions._id").is(new ObjectId(functionId)));
-
-        PetriNet petriNet = mongoTemplate.findOne(query, PetriNet.class, "petriNet");
-
-        Optional<Function> optionalFunction = petriNet.getFunctions().stream().filter(function -> function.getObjectId().toString().equals(functionId)).findFirst();
-
-        return optionalFunction.orElse(null);
+        if (functionId == null || functionId.isBlank() || !ObjectId.isValid(functionId)) {
+            throw new IllegalArgumentException("Invalid functionId: " + functionId);
+        }
+        ObjectId oid = new ObjectId(functionId);
+        Query query = new Query(Criteria.where("functions._id").is(oid));
+        // project only the matched array element to reduce payload
+        query.fields().elemMatch("functions", Criteria.where("_id").is(oid));
+
+        PetriNet petriNet = mongoTemplate.findOne(query, PetriNet.class, "petriNet");
+        if (petriNet == null || petriNet.getFunctions() == null || petriNet.getFunctions().isEmpty()) {
+            return null;
+        }
+        // after elemMatch the first item is the matched function
+        Function f = petriNet.getFunctions().getFirst();
+        return (f != null && oid.equals(f.getObjectId())) ? f : null;
     }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +16 to +17
void reloadCachedFunctions(String petriNetId);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Document the new overload and define its contract (evict vs. rebuild).

Clarify whether this method evicts and then rebuilds, or only rebuilds. Also specify expected ID format (24-hex) and error behavior.

Apply Javadoc near the signature:

+    /**
+     * Reloads (evict + rebuild) cached functions for the given PetriNet ID.
+     * The ID must be a valid 24-hex ObjectId.
+     * @throws IllegalArgumentException if {@code petriNetId} is invalid or the PetriNet does not exist
+     */
     void reloadCachedFunctions(String petriNetId);
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IFieldActionsCacheService.java
around lines 16 to 17, add Javadoc to the reloadCachedFunctions(String
petriNetId) overload that (1) states its contract clearly — whether it evicts
the existing cache entry and rebuilds it or only rebuilds in-place (choose and
document one behavior explicitly), (2) documents the expected petriNetId format
(24-character lowercase hex string) and validation behavior, (3) documents error
behavior (throw IllegalArgumentException for invalid id format,
NoSuchElementException if the referenced Petri net does not exist, and
Propagate/Log RuntimeException for rebuild failures), and (4) include any
thread-safety and performance notes (e.g., this method is blocking and may
acquire cache lock). Ensure the Javadoc is concise and placed immediately above
the method signature to define the contract unambiguously.

@Retoocs
Copy link
Contributor

Retoocs commented Sep 3, 2025

There are test errors

.collect(Collectors.toCollection(ArrayList::new));


Supplier<Map<String, Closure>> actionsFactory = () -> new MaxSizeHashMap<>(fieldRunnerProperties.getActionCacheSize());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MaxSizeHashMap is not a thread safe collection. Multiple threads can read and write to the cache at the same time

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing this out, I’ll fix it.

@dominikvozr dominikvozr marked this pull request as draft September 4, 2025 10:45
@Retoocs Retoocs self-requested a review September 4, 2025 10:48
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

♻️ Duplicate comments (9)
application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java (2)

21-32: Honor Cache.get contract; avoid NPE; remove unnecessary PetriNet fetch; handle contention.

  • Return null on miss, not ValueWrapper(null).
  • Use reload-by-id API; fetching PetriNet is unnecessary.
  • Prevent thundering herd by delegating to GenericMapCache.get(key, loader).

Apply:

     @Override
-    public ValueWrapper get(Object key) {
-        String stringKey = String.valueOf(key);
-
-        Object valueObject = map().get(stringKey);
-        if (valueObject != null) {
-            return new SimpleValueWrapper(List.copyOf((List<CachedFunction>) valueObject));
-        }
-        PetriNet petriNet = petriNetService.getPetriNet(stringKey);
-        fieldActionsCacheService.cachePetriNetFunctions(petriNet);
-        return new SimpleValueWrapper(List.copyOf((List<CachedFunction>) map().get(stringKey)));
-    }
+    public ValueWrapper get(Object key) {
+        if (key == null) throw new IllegalArgumentException("Cache key must not be null");
+        final String stringKey = key.toString();
+        List<CachedFunction> value = this.get(stringKey, () -> {
+            fieldActionsCacheService.reloadCachedFunctions(stringKey);
+            List<CachedFunction> v = (List<CachedFunction>) map().get(stringKey);
+            return v == null ? null : List.copyOf(v);
+        });
+        return value == null ? null : new SimpleValueWrapper(value);
+    }

34-46: Override typed get; mirror null-miss semantics; avoid PetriNet fetch.

Also add key validation and contention-safe loader.

Apply:

-    public <T> T get(Object key, Class<T> type) {
-        String stringKey = String.valueOf(key);
-        Object valueObject = map().get(stringKey);
-
-        if (valueObject != null) {
-            return type.cast(List.copyOf((List<CachedFunction>) valueObject));
-        }
-
-        PetriNet petriNet = petriNetService.getPetriNet(stringKey);
-        fieldActionsCacheService.cachePetriNetFunctions(petriNet);
-        return type.cast(List.copyOf((List<CachedFunction>) map().get(stringKey)));
-
-    }
+    @Override
+    public <T> T get(Object key, Class<T> type) {
+        if (key == null) throw new IllegalArgumentException("Cache key must not be null");
+        final String stringKey = key.toString();
+        List<CachedFunction> value = this.get(stringKey, () -> {
+            fieldActionsCacheService.reloadCachedFunctions(stringKey);
+            List<CachedFunction> v = (List<CachedFunction>) map().get(stringKey);
+            return v == null ? null : List.copyOf(v);
+        });
+        return value == null ? null : type.cast(value);
+    }
application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsMapCache.java (3)

15-22: Parameterize the cache; align with stored type; add @OverRide imports not needed.

Removes raw type usage and keeps type-safety.

Apply:

-@Slf4j
-public class FunctionsMapCache extends GenericMapCache {
+@Slf4j
+public class FunctionsMapCache extends GenericMapCache<CachedFunction> {
@@
-    public FunctionsMapCache(String name, java.util.function.Supplier<Map<String, CachedFunction>> mapFactory, IFieldActionsCacheService fieldActionsCacheService, IPetriNetService petriNetService, IGroovyShellFactory shellFactory) {
-        super(name, CachedFunction.class, mapFactory, fieldActionsCacheService, petriNetService);
+    public FunctionsMapCache(String name,
+                             java.util.function.Supplier<Map<String, CachedFunction>> mapFactory,
+                             IFieldActionsCacheService fieldActionsCacheService,
+                             IPetriNetService petriNetService,
+                             IGroovyShellFactory shellFactory) {
+        super(name, CachedFunction.class, mapFactory, fieldActionsCacheService, petriNetService);
         this.shell = shellFactory.getGroovyShell();
     }

25-40: Fix miss semantics; avoid double build; use super.put; validate key.

  • Return null when absent.
  • Build once on miss; reuse built instance for storage and response (or consistently return a copy).

Apply:

     @Override
     public synchronized ValueWrapper get(Object key) {
-        String stringKey = String.valueOf(key);
+        if (key == null) throw new IllegalArgumentException("Cache key must not be null");
+        String stringKey = key.toString();
 
         Object valueObject = map().get(stringKey);
         if (valueObject != null) {
-            return new SimpleValueWrapper(CachedFunction.copyOf(shell, (CachedFunction) valueObject));
+            return new SimpleValueWrapper(CachedFunction.copyOf(shell, (CachedFunction) valueObject));
         }
 
         Function function = petriNetService.findByFunctionId(stringKey);
         if (function != null) {
-            map().put(stringKey, CachedFunction.build(shell, function));
-            return new SimpleValueWrapper(CachedFunction.build(shell, function));
+            CachedFunction built = CachedFunction.build(shell, function);
+            super.put(stringKey, built);
+            return new SimpleValueWrapper(CachedFunction.copyOf(shell, built));
         }
 
-        return new SimpleValueWrapper(null);
+        return null;
     }

42-57: Override typed get; unify semantics with untyped; return null on miss.

Also avoid double build and write via super.put.

Apply:

-    public synchronized  <T> T get(Object key, Class<T> type) {
-        String stringKey = String.valueOf(key);
+    @Override
+    public synchronized <T> T get(Object key, Class<T> type) {
+        if (key == null) throw new IllegalArgumentException("Cache key must not be null");
+        String stringKey = key.toString();
         Object valueObject = map().get(stringKey);
 
         if (valueObject != null) {
-            return type.cast(valueObject);
+            return type.cast(CachedFunction.copyOf(shell, (CachedFunction) valueObject));
         }
 
         Function function = petriNetService.findByFunctionId(stringKey);
         if (function != null) {
-            map().put(stringKey, CachedFunction.build(shell, function));
-            return type.cast(CachedFunction.build(shell, function));
+            CachedFunction built = CachedFunction.build(shell, function);
+            super.put(stringKey, built);
+            return type.cast(CachedFunction.copyOf(shell, built));
         }
 
-        return type.cast(null);
+        return null;
     }
application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java (3)

12-12: Parameterize the cache with Closure to eliminate raw types

Extend GenericMapCache<Closure> for compile-time safety.

- public class ActionsMapCache extends GenericMapCache {
+ public class ActionsMapCache extends GenericMapCache<Closure> {

22-28: Honor Cache contract: return null on miss, don’t wrap null

Wrapping null in SimpleValueWrapper violates Spring’s Cache contract.

         Object valueObject = map().get(stringKey);
         if (valueObject != null) {
             return new SimpleValueWrapper(valueObject);
         }
         fieldActionsCacheService.reloadCachedFunctions(stringKey);
-        return new SimpleValueWrapper(map().get(stringKey));
+        Object refreshed = map().get(stringKey);
+        return refreshed != null ? new SimpleValueWrapper(refreshed) : null;

30-41: Add @OverRide and mirror null semantics for typed get

Also avoid type.cast(null) and return null directly.

-    public synchronized <T> T get(Object key, Class<T> type) {
+    @Override
+    public synchronized <T> T get(Object key, Class<T> type) {
         String stringKey = String.valueOf(key);
         Object valueObject = map().get(stringKey);
 
         if (valueObject != null) {
             return type.cast(valueObject);
         }
 
         fieldActionsCacheService.reloadCachedFunctions(stringKey);
-        return type.cast(map().get(stringKey));
+        Object refreshed = map().get(stringKey);
+        return refreshed != null ? type.cast(refreshed) : null;
 
     }

Run to locate similar violations across caches:

#!/bin/bash
rg -nP --type=java 'SimpleValueWrapper\\(null\\)|return\\s+type\\.cast\\(null\\)'
application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)

17-18: Tighten generics: use Class and make it final

Reduces unchecked casts and encodes the value type at the compiler level.

-    protected Class<?> valueType;
+    protected final Class<V> valueType;
@@
-    public GenericMapCache(String name, Class<?> valueType, java.util.function.Supplier<Map<String, V>> mapFactory, IFieldActionsCacheService fieldActionsCacheService, IPetriNetService petriNetService) {
+    public GenericMapCache(String name, Class<V> valueType, java.util.function.Supplier<Map<String, V>> mapFactory, IFieldActionsCacheService fieldActionsCacheService, IPetriNetService petriNetService) {

Also applies to: 26-26

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bd719b5 and e976081.

📒 Files selected for processing (6)
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java (1 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java (1 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsMapCache.java (1 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java (1 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/domain/CachedFunction.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsMapCache.java (4)
application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java (1)
  • Slf4j (11-42)
application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java (1)
  • Slf4j (14-53)
application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)
  • Slf4j (14-104)
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (1)
  • Slf4j (66-581)
application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (4)
application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java (1)
  • Slf4j (11-42)
application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsMapCache.java (1)
  • Slf4j (14-58)
application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java (1)
  • Slf4j (14-53)
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (1)
  • Slf4j (66-581)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java (1)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java (1)
  • CacheMapKeys (3-7)
application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java (4)
application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsMapCache.java (1)
  • Slf4j (14-58)
application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java (1)
  • Slf4j (14-53)
application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)
  • Slf4j (14-104)
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (1)
  • Slf4j (66-581)
application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java (4)
application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java (1)
  • Slf4j (11-42)
application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsMapCache.java (1)
  • Slf4j (14-58)
application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)
  • Slf4j (14-104)
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (1)
  • Slf4j (66-581)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Test
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
🔇 Additional comments (1)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java (1)

53-82: Good: bounded maps wrapped with synchronization

Wrapping MaxSizeHashMap with Collections.synchronizedMap addresses immediate thread-safety concerns for shared caches.

Comment on lines 18 to 19
@Override
public synchronized ValueWrapper get(Object key) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix return type reference: unresolved ValueWrapper

ValueWrapper is an inner type of org.springframework.cache.Cache; reference it properly to fix compilation.

-    public synchronized ValueWrapper get(Object key) {
+    public synchronized org.springframework.cache.Cache.ValueWrapper get(Object key) {

Add this import outside the shown range (either one of the two):

  • import org.springframework.cache.Cache;
  • or import org.springframework.cache.Cache.ValueWrapper;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Override
public synchronized ValueWrapper get(Object key) {
@Override
public synchronized org.springframework.cache.Cache.ValueWrapper get(Object key) {
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java
around lines 18-19, the method signature uses ValueWrapper but the type is
unresolved because ValueWrapper is an inner type of
org.springframework.cache.Cache; fix by adding an import for the inner type
(import org.springframework.cache.Cache.ValueWrapper;) or import the outer type
(import org.springframework.cache.Cache;) and reference the type explicitly as
Cache.ValueWrapper in the signature; add the chosen import at the top of the
file (outside the shown range) so the method compiles.

Comment on lines 53 to 82
Supplier<Map<String, Closure>> actionsFactory =
() -> Collections.synchronizedMap(new MaxSizeHashMap<>(fieldRunnerProperties.getActionCacheSize()));

caches.add(new ActionsMapCache(
CacheMapKeys.ACTIONS,
actionsFactory,
fieldActionsCacheService,
petriNetService
));

Supplier<Map<String, CachedFunction>> functionsFactory =
() -> Collections.synchronizedMap(new MaxSizeHashMap<>(fieldRunnerProperties.getFunctionsCacheSize()));

caches.add(new FunctionsMapCache(
CacheMapKeys.FUNCTIONS,
functionsFactory,
fieldActionsCacheService,
petriNetService,
groovyShellFactory
));

Supplier<Map<String, List<CachedFunction>>> nsFactory =
() -> Collections.synchronizedMap(new MaxSizeHashMap<>(fieldRunnerProperties.getNamespaceCacheSize()));

caches.add(new FunctionsNamespaceMapCache(
CacheMapKeys.NAMESPACE_FUNCTIONS,
nsFactory,
fieldActionsCacheService,
petriNetService
));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Consider Caffeine for production-grade bounded caches

Caffeine gives O(1) concurrency, admission/eviction policies, and metrics; wire CaffeineCache instances into SimpleCacheManager as a follow-up.

Comment on lines 28 to 31
Object valueObject = map().get(stringKey);
if (valueObject != null) {
return new SimpleValueWrapper(CachedFunction.copyOf(shell, (CachedFunction) valueObject));
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Consider eliminating per-call copyOf for performance.

Re-evaluating Groovy on every hit undermines caching. If thread-safety allows, return the stored CachedFunction directly, or cache compiled classes and rehydrate closures cheaply. If you must copy, consider Closure#rehydrate over full re-eval.

🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsMapCache.java
around lines 28-31, the code currently calls CachedFunction.copyOf(...) on every
cache hit which forces Groovy re-evaluation and kills performance; instead, if
CachedFunction is immutable or can be made thread-safe, return the stored
CachedFunction directly from the cache; otherwise change the cache to store a
compiled/rehydratable Closure or class and create lightweight per-call wrappers
via Closure.rehydrate (or a cached compiled form) rather than full re-eval, or
implement copy-on-write so copies are made only on mutating calls — pick one
approach, make CachedFunction immutable or mark it thread-safe, update the cache
return to return the stored instance when safe, or update the cache value to a
compiled/rehydratable closure and create lightweight closures on read.

Comment on lines 15 to 19
public class FunctionsNamespaceMapCache extends GenericMapCache {

public FunctionsNamespaceMapCache(String name, java.util.function.Supplier<Map<String, List<CachedFunction>>> mapFactory, IFieldActionsCacheService fieldActionsCacheService, IPetriNetService petriNetService) {
super(name, List.class, mapFactory, fieldActionsCacheService, petriNetService);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Parameterize the cache type; avoid raw GenericMapCache.

Prevents unsafe casts and lets GenericMapCache.safeCast help.

Apply:

-@Slf4j
-public class FunctionsNamespaceMapCache extends GenericMapCache {
+@Slf4j
+public class FunctionsNamespaceMapCache extends GenericMapCache<List<CachedFunction>> {
@@
-    public FunctionsNamespaceMapCache(String name, java.util.function.Supplier<Map<String, List<CachedFunction>>> mapFactory, IFieldActionsCacheService fieldActionsCacheService, IPetriNetService petriNetService) {
-        super(name, List.class, mapFactory, fieldActionsCacheService, petriNetService);
-    }
+    public FunctionsNamespaceMapCache(String name,
+                                      java.util.function.Supplier<Map<String, List<CachedFunction>>> mapFactory,
+                                      IFieldActionsCacheService fieldActionsCacheService,
+                                      IPetriNetService petriNetService) {
+        super(name, List.class, mapFactory, fieldActionsCacheService, petriNetService);
+    }
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java
around lines 15–19, the class currently extends the raw GenericMapCache which
causes unsafe casts; change the class to extend GenericMapCache<Map<String,
List<CachedFunction>>> and update the constructor signature to accept
Supplier<Map<String, List<CachedFunction>>>; in the super(...) call replace the
raw List.class with a properly typed Class for Map (use
GenericMapCache.safeCast(Map.class) or an equivalent safeCast to produce a
Class<Map<String, List<CachedFunction>>>), ensuring all generics line up so no
raw types remain and unsafe casting is avoided.

Comment on lines 48 to 52
@Override
public void put(Object key, Object value) {
String k = String.valueOf(key);
map().put(k, List.copyOf((List<CachedFunction>) value));
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Use super.put and enforce immutability once.

Avoid bypassing safeCast; still store an unmodifiable List.

Apply:

     @Override
     public void put(Object key, Object value) {
-        String k = String.valueOf(key);
-        map().put(k, List.copyOf((List<CachedFunction>) value));
+        if (key == null) throw new IllegalArgumentException("Cache key must not be null");
+        super.put(key, List.copyOf((List<CachedFunction>) value));
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Override
public void put(Object key, Object value) {
String k = String.valueOf(key);
map().put(k, List.copyOf((List<CachedFunction>) value));
}
@Override
public void put(Object key, Object value) {
if (key == null) throw new IllegalArgumentException("Cache key must not be null");
super.put(key, List.copyOf((List<CachedFunction>) value));
}
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java
around lines 48-52, the current implementation bypasses safeCast and writes
directly to map(); change it to call super.put(key, value) and ensure
immutability exactly once: use the class's safeCast (or equivalent) to cast the
incoming value to List<CachedFunction>, wrap that result with List.copyOf(...)
to make it unmodifiable, and pass that wrapped list into super.put(key,
wrappedList) instead of map().put(...).

Comment on lines 27 to 31
public static CachedFunction copyOf(GroovyShell shell, CachedFunction cachedFunction) {
if (cachedFunction == null) {
return null;
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Guard against null input early (returning null here may mask bugs).

Prefer failing fast on null input or document the nullable contract. I recommend throwing IllegalArgumentException for null cachedFunction to avoid silent nulls propagating.

Apply:

-    public static CachedFunction copyOf(GroovyShell shell, CachedFunction cachedFunction) {
-        if (cachedFunction == null) {
-            return null;
-        }
+    public static CachedFunction copyOf(GroovyShell shell, CachedFunction cachedFunction) {
+        if (cachedFunction == null) {
+            throw new IllegalArgumentException("cachedFunction must not be null");
+        }
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/workflow/domain/CachedFunction.java
around lines 27 to 31, the copyOf method currently returns null when
cachedFunction is null which can hide errors; change the method to fail fast by
throwing an IllegalArgumentException when cachedFunction is null (with a clear
message like "cachedFunction must not be null") instead of returning null so
callers immediately see the contract violation.

Comment on lines 32 to 36
Closure code = (Closure) shell.evaluate(cachedFunction.getFunction().getDefinition());
if (code == null) {
throw new IllegalArgumentException("Non compilable function");
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Harden evaluation: null/blank definition, non-Closure result, and exceptions.

Current code may NPE on null definition and ClassCastException on non-Closure. Wrap compilation errors consistently.

Apply:

-        Closure code = (Closure) shell.evaluate(cachedFunction.getFunction().getDefinition());
-        if (code == null) {
-            throw new IllegalArgumentException("Non compilable function");
-        }
+        String def = cachedFunction.getFunction() != null ? cachedFunction.getFunction().getDefinition() : null;
+        if (def == null || def.isBlank()) {
+            throw new IllegalArgumentException("Function definition must not be null/blank");
+        }
+        final Object evaluated;
+        try {
+            evaluated = shell.evaluate(def);
+        } catch (Throwable t) {
+            throw new IllegalArgumentException("Non compilable function", t);
+        }
+        if (!(evaluated instanceof Closure)) {
+            throw new IllegalArgumentException("Function definition did not evaluate to a Closure");
+        }
+        Closure code = (Closure) evaluated;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Closure code = (Closure) shell.evaluate(cachedFunction.getFunction().getDefinition());
if (code == null) {
throw new IllegalArgumentException("Non compilable function");
}
String def = cachedFunction.getFunction() != null
? cachedFunction.getFunction().getDefinition()
: null;
if (def == null || def.isBlank()) {
throw new IllegalArgumentException("Function definition must not be null/blank");
}
final Object evaluated;
try {
evaluated = shell.evaluate(def);
} catch (Throwable t) {
throw new IllegalArgumentException("Non compilable function", t);
}
if (!(evaluated instanceof Closure)) {
throw new IllegalArgumentException("Function definition did not evaluate to a Closure");
}
Closure code = (Closure) evaluated;
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/workflow/domain/CachedFunction.java
around lines 32 to 36, harden the evaluation: first validate
cachedFunction.getFunction().getDefinition() for null or blank and throw
IllegalArgumentException with a clear message if invalid; then call
shell.evaluate(...) inside a try/catch to catch and wrap any compilation/runtime
exceptions into an IllegalArgumentException that includes the original
exception; after evaluation verify the returned object is a Closure (not null or
another type) and if not throw IllegalArgumentException indicating non-Closure
result (including actual result type when available).

@dominikvozr dominikvozr self-assigned this Sep 9, 2025
…namespaceFunctionsCache from local Map to Redis
@dominikvozr dominikvozr marked this pull request as ready for review September 16, 2025 09:02
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (8)
application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)

59-61: Fix compile error: qualify ValueRetrievalException

It’s an inner type of Cache.

-        } catch (Exception ex) {
-            throw new ValueRetrievalException(stringKey, loader, ex);
+        } catch (Exception ex) {
+            throw new Cache.ValueRetrievalException(stringKey, loader, ex);
         }
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (6)

58-65: Fail fast on missing namespace functions cache

Use a required-cache helper to avoid NPEs and produce clear errors.

-        Cache namespaceFunctionsCache = cacheManager.getCache(properties.getNamespaceFunctions());
+        Cache namespaceFunctionsCache = getRequiredCache(properties.getNamespaceFunctions());

149-151: Return a typed copy of the namespace cache; avoid raw casts

Safer and future‑proof.

-        return new HashMap<>((Map) cacheManager.getCache(properties.getNamespaceFunctions()).getNativeCache());
+        @SuppressWarnings("unchecked")
+        Map<String, List<CachedFunction>> nativeMap =
+                (Map<String, List<CachedFunction>>) getRequiredCache(properties.getNamespaceFunctions()).getNativeCache();
+        return new HashMap<>(nativeMap);

154-166: Clear caches via required-cache helper

Prevents NPEs if a cache is missing.

-        cacheManager.getCache(CacheMapKeys.ACTIONS).clear();
+        getRequiredCache(CacheMapKeys.ACTIONS).clear();
@@
-        cacheManager.getCache(properties.getNamespaceFunctions()).clear();
+        getRequiredCache(properties.getNamespaceFunctions()).clear();
@@
-        cacheManager.getCache(CacheMapKeys.FUNCTIONS).clear();
+        getRequiredCache(CacheMapKeys.FUNCTIONS).clear();

69-72: Guard reload path with required-cache helper

Prevents NPE when cache is misconfigured.

-        cacheManager.getCache(properties.getNamespaceFunctions()).evictIfPresent(petriNetId);
+        getRequiredCache(properties.getNamespaceFunctions()).evictIfPresent(petriNetId);
         cachePetriNetFunctions(petriNetService.getNewestVersionByIdentifier(petriNetId));

80-92: Use atomic Cache#get(key, loader) and evict on rewrite; stop peeking into native map

This fixes potential NPE and races, and honors rewrite semantics.

-        Cache actionsCache = cacheManager.getCache(CacheMapKeys.ACTIONS);
-        Object nativeActionsCache = actionsCache.getNativeCache();
-
-        if (nativeActionsCache instanceof Map<?, ?> map) {
-            if (shouldRewriteCachedActions || !map.containsKey(stringId) ) {
-                Closure code = (Closure) shell.evaluate("{-> " + action.getDefinition() + "}");
-                actionsCache.put(stringId, code);
-            }
-        }
-        return (Closure) actionsCache.get(stringId).get();
+        Cache actionsCache = getRequiredCache(CacheMapKeys.ACTIONS);
+        if (shouldRewriteCachedActions) {
+            actionsCache.evict(stringId);
+        }
+        return actionsCache.get(stringId, () -> (Closure) shell.evaluate("{-> " + action.getDefinition() + "}"));

95-107: Compute functions via Cache API, no native map casting

Cleaner, thread-safe, and backend-agnostic.

-        Cache functionsCache = cacheManager.getCache(CacheMapKeys.FUNCTIONS);
-        Object nativeFunctionsCache = functionsCache.getNativeCache();
-
-        if (nativeFunctionsCache instanceof Map<?, ?> map) {
-            functions.forEach(function -> {
-                if (!map.containsKey(function.getStringId())) {
-                    functionsCache.put(function.getStringId(), CachedFunction.build(shell, function));
-                }
-                cachedFunctions.add((CachedFunction) functionsCache.get(function.getStringId()).get());
-            });
-        }
+        Cache functionsCache = getRequiredCache(CacheMapKeys.FUNCTIONS);
+        for (Function function : functions) {
+            CachedFunction cf = functionsCache.get(function.getStringId(),
+                    () -> CachedFunction.build(shell, function));
+            cachedFunctions.add(cf);
+        }
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java (1)

38-42: Avoid duplicate registrations for specialized caches

Filter out ACTIONS and FUNCTIONS from the generic cache list before adding specialized implementations.

-        Set<String> cacheNames = properties.getAllCaches();
-        List<Cache> caches = cacheNames.stream()
+        Set<String> cacheNames = new LinkedHashSet<>(properties.getAllCaches());
+        cacheNames.removeAll(Set.of(CacheMapKeys.ACTIONS, CacheMapKeys.FUNCTIONS));
+        List<Cache> caches = cacheNames.stream()
                 .map(ConcurrentMapCache::new)
                 .collect(Collectors.toCollection(ArrayList::new));
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e976081 and 8323adb.

📒 Files selected for processing (5)
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java (1 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java (1 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java (2 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java (1)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java (1)
  • CacheMapKeys (3-6)
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (3)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java (1)
  • CacheMapKeys (3-6)
application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)
  • Slf4j (12-122)
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (1)
  • Slf4j (66-581)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: task-list-completed
  • GitHub Check: Test
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
🔇 Additional comments (1)
application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java (1)

44-48: LGTM on adding namespaceFunctions property and wiring it into getAllCaches()

Also applies to: 62-63

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8323adb and f9796d6.

📒 Files selected for processing (4)
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java (1 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (2 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-31T23:40:46.499Z
Learnt from: tuplle
PR: netgrif/application-engine#334
File: application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java:204-214
Timestamp: 2025-07-31T23:40:46.499Z
Learning: In the PetriNetService.importPetriNet method, existingNet.getVersion() cannot be null because all existing nets in the system were deployed through processes that ensure every net always has a version assigned.

Applied to files:

  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: Build
🔇 Additional comments (4)
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (2)

567-567: No functional change here.

Whitespace-only change; nothing to address.


8-8: Cache names match — no action required.
@Cacheable values in PetriNetService ("petriNetCache" — line 158; "petriNetById" — 250; "petriNetByIdentifier" — 261; "petriNetNewest" — 284) exactly match defaults in CacheConfigurationProperties (lines 22, 27, 32, 37).

application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java (1)

3-7: LGTM — constants holder is correctly non-instantiable.
Matches prior guidance: final class + private ctor; fields are public static final. No issues.

application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)

33-39: No action required — factories already return synchronized maps. CacheConfiguration creates GenericMapCache instances with Suppliers that wrap MaxSizeHashMap via Collections.synchronizedMap (application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java:45–58), so the current unsynchronized reads are safe.

Comment on lines 30 to 31
@Override public Object getNativeCache() { return Map.copyOf(map()); }

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick

Preserve native cache identity; clear in place.
Returning Map.copyOf breaks “native” semantics and reassigning the map on clear invalidates existing nativeCache holders. Return the backing map and clear it in place.

Apply this diff:

-    @Override public Object getNativeCache() { return Map.copyOf(map()); }
+    @Override public Object getNativeCache() { return map(); }
@@
-    public synchronized void clear() {
-        this.atomicMapRef.set(mapFactory.get());
-    }
+    public synchronized void clear() {
+        map().clear();
+        log.debug("{} cache cleared", name);
+    }

Also applies to: 95-98

🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java
around lines 30-31 (and likewise adjust lines 95-98), the getNativeCache method
currently returns Map.copyOf(map()) which breaks native cache identity; change
getNativeCache to return the backing map directly (return map()) so callers keep
the same instance. Also modify clear logic (lines ~95-98) to clear the existing
backing map in place (call map().clear()) instead of reassigning a new map
instance, preserving nativeCache holders and expected semantics.

@renczesstefan renczesstefan changed the base branch from release/7.0.0-rev7 to release/7.0.0-rev8 September 23, 2025 07:31
Copy link
Member

@renczesstefan renczesstefan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please review coderabbitAI comments.

private final CacheConfigurationProperties properties;

public CacheConfiguration(CacheConfigurationProperties properties) {
public CacheConfiguration(RunnerConfigurationProperties.FieldRunnerProperties fieldRunnerProperties, CacheConfigurationProperties properties) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its only cosmetic, but instead of this constructor you can use @RequiredArgsConstructor over the class.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you. I will fix that.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/petrinet/domain/PetriNet.java (1)

63-107: Consider removing commented-out code or creating a tracked issue.

The TODO comment on already commented-out code (44 lines) is a code smell. If the clone method is not needed, remove it now rather than deferring the decision. If there's genuine uncertainty, create a proper JIRA ticket to track the evaluation.

Would you like me to help evaluate whether this clone method is used anywhere in the codebase, or would you prefer to remove it immediately and rely on the superclass implementation?

🔎 If the clone method is confirmed as unused, apply this diff to remove the dead code:
-    // todo: delete clone method if not needed
-//    @Override
-//    public PetriNet clone() {
-//        PetriNet clone = new PetriNet();
-//        clone.setIdentifier(this.getIdentifier());
-//        clone.setUriNodeId(this.getUriNodeId());
-//        clone.setInitials(this.getInitials());
-//        clone.setTitle(this.getTitle().clone());
-//        clone.setDefaultRoleEnabled(this.isDefaultRoleEnabled());
-//        clone.setDefaultCaseName(this.getDefaultCaseName() == null ? null : this.getDefaultCaseName().clone());
-//        clone.setDefaultCaseNameExpression(this.getDefaultCaseNameExpression() == null ? null : this.getDefaultCaseNameExpression().clone());
-//        clone.setIcon(this.getIcon());
-//        clone.setCreationDate(this.getCreationDate());
-//        clone.setVersion(this.getVersion() == null ? null : this.getVersion().clone());
-//        clone.setAuthor(this.getAuthor() == null ? null : this.getAuthor().clone());
-//        clone.setTransitions(this.getTransitions() == null ? null : this.getTransitions().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> new com.netgrif.application.engine.adapter.spring.petrinet.domain.Transition(e.getValue()), (v1, v2) -> v1, LinkedHashMap::new)));
-//        clone.setRoles(this.roles == null ? null : this.roles.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> new com.netgrif.application.engine.adapter.spring.petrinet.domain.roles.ProcessRole(e.getValue()), (v1, v2) -> v1, LinkedHashMap::new)));
-//        clone.setTransactions(this.getTransactions() == null ? null : this.getTransactions().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> new com.netgrif.application.engine.adapter.spring.petrinet.domain.Transaction(e.getValue()), (v1, v2) -> v1, LinkedHashMap::new)));
-//        clone.setImportXmlPath(this.getImportXmlPath());
-//        clone.setImportId(this.importId);
-//        clone.setObjectId(this._id);
-//        clone.setDataSet(this.getDataSet().entrySet()
-//                .stream()
-//                .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().clone(), (x, y)->y, LinkedHashMap::new))
-//        );
-//        clone.setPlaces(this.getPlaces().entrySet()
-//                .stream()
-//                .collect(Collectors.toMap(Map.Entry::getKey, e -> new Place(e.getValue()), (x, y)->y, LinkedHashMap::new))
-//        );
-//        clone.setArcs(this.getArcs().entrySet()
-//                .stream()
-//                .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().stream()
-//                        .map(Arc::clone)
-//                        .collect(Collectors.toList()), (x,y)->y, LinkedHashMap::new))
-//        );
-//        clone.initializeArcs();
-//        clone.setCaseEvents(this.getCaseEvents() == null ? null : this.getCaseEvents().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().clone())));
-//        clone.setProcessEvents(this.getProcessEvents() == null ? null : this.getProcessEvents().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().clone())));
-//        clone.setPermissions(this.getPermissions() == null ? null : this.getPermissions().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> new HashMap<>(e.getValue()))));
-//        clone.setUserRefs(this.getUserRefs() == null ? null : this.getUserRefs().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> new HashMap<>(e.getValue()))));
-//        this.getNegativeViewRoles().forEach(clone::addNegativeViewRole);
-//        this.getFunctions().forEach(clone::addFunction);
-//        clone.setTags(new HashMap<>(this.getTags()));
-//        return clone;
-//    }
♻️ Duplicate comments (8)
application-engine/src/main/resources/application.yaml (1)

119-121: Critical security risk: All actuator endpoints publicly accessible.

Line 121 exposes all actuator endpoints (include: "*"), and line 89 permits anonymous access to /manage/**. This combination makes sensitive endpoints (env, heapdump, beans, caches, etc.) publicly accessible without authentication.

This concern was previously raised and includes recommended fixes to restrict exposure to only required endpoints and remove or secure /manage/** from anonymous access.

application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java (1)

4-4: Avoid wildcard imports; restore explicit imports.

Wildcard imports violate common style guidelines, can hide accidental type collisions, and make IDE organize-imports unstable. Please use explicit imports for the actually used types (PetriNet, PetriNetSearch, Transition, VersionType).

🔎 Apply this diff:
-import com.netgrif.application.engine.objects.petrinet.domain.*;
+import com.netgrif.application.engine.objects.petrinet.domain.PetriNet;
+import com.netgrif.application.engine.objects.petrinet.domain.PetriNetSearch;
+import com.netgrif.application.engine.objects.petrinet.domain.Transition;
+import com.netgrif.application.engine.objects.petrinet.domain.VersionType;

Based on past review comments, wildcard imports should be avoided in this codebase.

application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IFieldActionsCacheService.java (1)

16-17: Add Javadoc for the new reloadCachedFunctions(String petriNetId) overload.

This method lacks documentation specifying its contract, expected ID format, and error behavior. As noted in a previous review, clarify whether it evicts and rebuilds or only rebuilds, and document the expected format and error handling.

📝 Apply suggested Javadoc
+    /**
+     * Reloads (evicts and rebuilds) cached functions for the PetriNet with the given ID.
+     * 
+     * @param petriNetId the MongoDB ObjectId (24-character hex string) of the PetriNet
+     * @throws IllegalArgumentException if the ID format is invalid or the PetriNet does not exist
+     */
     void reloadCachedFunctions(String petriNetId);
application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (2)

90-93: clear() replaces the map reference instead of clearing in-place.

This approach has trade-offs:

  • Pro: Ongoing operations on the old map complete without interference
  • Con: Any code holding a reference to getNativeCache() (if it returned the backing map) would have a stale reference

Since getNativeCache() returns a copy, this is less problematic, but it's inconsistent with typical cache semantics where clearing affects the same backing store.


31-31: getNativeCache() returns a defensive copy, breaking the Spring Cache contract.

The Spring Cache contract expects getNativeCache() to return the actual backing store. Returning Map.copyOf() means callers (like FieldActionsCacheService.getGlobalFunctionsCache()) receive a snapshot that doesn't reflect subsequent changes, and the instanceof Map checks in getCompiledAction/getCachedFunctions operate on a detached copy.

If defensive copying is intentional for safety, document this deviation. Otherwise, return the backing map directly.

🔎 Suggested fix:
-    @Override public @NotNull Object getNativeCache() { return Map.copyOf(map()); }
+    @Override public @NotNull Object getNativeCache() { return map(); }
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (2)

97-112: Same TOCTOU race and NPE risk in getCachedFunctions.

Similar to getCompiledAction, this method checks containsKey on a snapshot (Map.copyOf) but operates on the actual cache. Use Cache#get(key, Callable) for atomic operations:

🔎 Suggested fix:
     @Override
     public List<CachedFunction> getCachedFunctions(List<Function> functions) {
         List<CachedFunction> cachedFunctions = new ArrayList<>(functions.size());
         Cache functionsCache = getRequiredCache(CacheMapKeys.FUNCTIONS);
-        Object nativeFunctionsCache = functionsCache.getNativeCache();
-
-        if (nativeFunctionsCache instanceof Map<?, ?> map) {
-            functions.forEach(function -> {
-                if (!map.containsKey(function.getStringId())) {
-                    functionsCache.put(function.getStringId(), CachedFunction.build(shell, function));
-                }
-                cachedFunctions.add((CachedFunction) functionsCache.get(function.getStringId()).get());
-            });
-        }
+        for (Function function : functions) {
+            CachedFunction cf = functionsCache.get(function.getStringId(),
+                    () -> CachedFunction.build(shell, function));
+            cachedFunctions.add(cf);
+        }
         return cachedFunctions;
     }

82-95: TOCTOU race and potential NPE in getCompiledAction.

Two issues:

  1. TOCTOU race: getNativeCache() returns Map.copyOf() (a snapshot), so !map.containsKey(stringId) checks a stale copy while actionsCache.put() writes to the actual cache. Under concurrent access, multiple threads may redundantly compile the same action.

  2. Potential NPE at line 94: If the instanceof check fails (cache impl changed), the method falls through without populating the cache, then actionsCache.get(stringId).get() returns null.get().

Use the Cache#get(key, Callable) API for atomic compute-if-absent:

🔎 Suggested fix:
     @Override
     public Closure getCompiledAction(Action action, boolean shouldRewriteCachedActions) {
         String stringId = action.getId().toString();
         Cache actionsCache = getRequiredCache(CacheMapKeys.ACTIONS);
-        Object nativeActionsCache = actionsCache.getNativeCache();
-
-        if (nativeActionsCache instanceof Map<?, ?> map) {
-            if (shouldRewriteCachedActions || !map.containsKey(stringId) ) {
-                Closure code = (Closure) shell.evaluate("{-> " + action.getDefinition() + "}");
-                actionsCache.put(stringId, code);
-            }
+        if (shouldRewriteCachedActions) {
+            actionsCache.evict(stringId);
         }
-        return (Closure) actionsCache.get(stringId).get();
+        return actionsCache.get(stringId, () -> (Closure) shell.evaluate("{-> " + action.getDefinition() + "}"));
     }
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java (1)

36-62: Potential duplicate cache registration if additional caches include "actions" or "functions".

The code creates ConcurrentMapCache instances for all names from properties.getAllCaches(), then adds GenericMapCache instances for CacheMapKeys.ACTIONS and CacheMapKeys.FUNCTIONS. If a user configures additional caches containing "actions" or "functions", SimpleCacheManager will have duplicate cache names with different implementations.

Consider filtering out the specialized cache names before mapping:

🔎 Suggested fix:
     public CacheManager cacheManager() {
         Set<String> cacheNames = properties.getAllCaches();
+        cacheNames.remove(CacheMapKeys.ACTIONS);
+        cacheNames.remove(CacheMapKeys.FUNCTIONS);
         List<Cache> caches = cacheNames.stream()
                 .map(ConcurrentMapCache::new)
                 .collect(Collectors.toCollection(ArrayList::new));
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cb1505f and f7b61be.

📒 Files selected for processing (44)
  • application-engine/pom.xml (0 hunks)
  • application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy (1 hunks)
  • application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/FieldActionsRunner.groovy (1 hunks)
  • application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/delegate/RoleActionDelegate.groovy (2 hunks)
  • application-engine/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovy (2 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java (1 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java (3 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java (1 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/importer/service/FunctionFactory.java (3 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.java (2 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardManagementServiceImpl.java (2 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/repositories/PetriNetRepository.java (1 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (8 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java (2 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/startup/runner/DefaultFiltersRunner.java (1 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/startup/runner/ImpersonationRunner.java (1 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java (1 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (4 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FilterImportExportService.java (1 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.java (2 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java (3 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IFieldActionsCacheService.java (1 hunks)
  • application-engine/src/main/resources/application.yaml (1 hunks)
  • application-engine/src/test/groovy/com/netgrif/application/engine/TestHelper.groovy (1 hunks)
  • application-engine/src/test/groovy/com/netgrif/application/engine/export/service/ExportServiceTest.groovy (2 hunks)
  • application-engine/src/test/groovy/com/netgrif/application/engine/impersonation/ImpersonationServiceTest.groovy (1 hunks)
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/ImporterTest.groovy (2 hunks)
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/PetriNetTest.groovy (1 hunks)
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/CachePetriNetServiceTest.groovy (1 hunks)
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovy (5 hunks)
  • application-engine/src/test/groovy/com/netgrif/application/engine/workflow/NewInitTest.groovy (1 hunks)
  • application-engine/src/test/resources/change_caseref_value_action_test.xml (2 hunks)
  • application-engine/src/test/resources/petriNets/NAE-1290_Export_actions.xml (1 hunks)
  • application-engine/src/test/resources/petriNets/process_version_1_0_0.xml (1 hunks)
  • application-engine/src/test/resources/petriNets/process_version_2_0_0.xml (1 hunks)
  • application-engine/src/test/resources/petriNets/process_version_3_0_0.xml (1 hunks)
  • application-engine/src/test/resources/petriNets/process_version_4_0_0.xml (1 hunks)
  • application-engine/src/test/resources/petriNets/process_version_5_0_0.xml (1 hunks)
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/elastic/domain/ElasticPetriNet.java (3 hunks)
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/FunctionScope.java (1 hunks)
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/PetriNet.java (6 hunks)
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/version/Version.java (2 hunks)
  • nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/petrinet/domain/PetriNet.java (1 hunks)
💤 Files with no reviewable changes (1)
  • application-engine/pom.xml
🧰 Additional context used
🧠 Learnings (11)
📚 Learning: 2025-07-31T23:40:46.499Z
Learnt from: tuplle
Repo: netgrif/application-engine PR: 334
File: application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java:204-214
Timestamp: 2025-07-31T23:40:46.499Z
Learning: In the PetriNetService.importPetriNet method, existingNet.getVersion() cannot be null because all existing nets in the system were deployed through processes that ensure every net always has a version assigned.

Applied to files:

  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/repositories/PetriNetRepository.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java
  • application-engine/src/test/resources/petriNets/NAE-1290_Export_actions.xml
  • application-engine/src/test/groovy/com/netgrif/application/engine/export/service/ExportServiceTest.groovy
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java
  • application-engine/src/test/resources/petriNets/process_version_5_0_0.xml
  • application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardManagementServiceImpl.java
  • application-engine/src/test/resources/petriNets/process_version_1_0_0.xml
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java
  • application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy
  • nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/petrinet/domain/PetriNet.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FilterImportExportService.java
  • application-engine/src/main/java/com/netgrif/application/engine/startup/runner/ImpersonationRunner.java
  • application-engine/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovy
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/PetriNet.java
  • application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.java
  • application-engine/src/test/resources/petriNets/process_version_2_0_0.xml
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/ImporterTest.groovy
  • application-engine/src/main/java/com/netgrif/application/engine/startup/runner/DefaultFiltersRunner.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.java
  • application-engine/src/test/resources/petriNets/process_version_3_0_0.xml
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/PetriNetTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/CachePetriNetServiceTest.groovy
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/elastic/domain/ElasticPetriNet.java
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovy
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
  • application-engine/src/test/resources/change_caseref_value_action_test.xml
  • application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/delegate/RoleActionDelegate.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/workflow/NewInitTest.groovy
📚 Learning: 2025-12-12T13:40:00.786Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 397
File: application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java:98-104
Timestamp: 2025-12-12T13:40:00.786Z
Learning: In application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java, the permission constraint logic in buildQuery() intentionally applies negative actor constraints globally: ((roleConstraint AND NOT negRoleConstraint) OR actorConstraint) AND NOT negActorConstraint. This means users in negativeViewActors are excluded even if they have positive role permissions, which is the intended authorization behavior.

Applied to files:

  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java
  • application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardManagementServiceImpl.java
  • application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.java
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovy
📚 Learning: 2025-12-12T12:36:06.651Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 397
File: application-engine/src/main/resources/petriNets/engine-processes/org_group.xml:36-39
Timestamp: 2025-12-12T12:36:06.651Z
Learning: In the org_group.xml file (application-engine/src/main/resources/petriNets/engine-processes/org_group.xml), variable names in action blocks like userField and user_selection should be kept as-is even when the field type changes from "user" to "actor".

Applied to files:

  • application-engine/src/test/resources/petriNets/NAE-1290_Export_actions.xml
  • application-engine/src/test/resources/petriNets/process_version_5_0_0.xml
  • application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardManagementServiceImpl.java
  • application-engine/src/test/resources/petriNets/process_version_4_0_0.xml
  • application-engine/src/test/resources/petriNets/process_version_1_0_0.xml
  • application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy
  • application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.java
  • application-engine/src/test/resources/petriNets/process_version_2_0_0.xml
  • application-engine/src/test/resources/petriNets/process_version_3_0_0.xml
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
  • application-engine/src/test/resources/change_caseref_value_action_test.xml
  • application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/delegate/RoleActionDelegate.groovy
📚 Learning: 2025-09-05T10:21:54.893Z
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 350
File: application-engine/src/test/groovy/com/netgrif/application/engine/export/service/ExportServiceTest.groovy:135-135
Timestamp: 2025-09-05T10:21:54.893Z
Learning: In ExportServiceTest.groovy, writing to src/test/resources is intentional to simulate production behavior where the working tree is mutated during file exports. This mirrors how the system works in production.

Applied to files:

  • application-engine/src/test/resources/petriNets/NAE-1290_Export_actions.xml
  • application-engine/src/test/groovy/com/netgrif/application/engine/export/service/ExportServiceTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/PetriNetTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovy
📚 Learning: 2025-12-12T12:37:58.852Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 397
File: application-engine/src/test/groovy/com/netgrif/application/engine/auth/WorkflowAuthorizationServiceTest.groovy:4-4
Timestamp: 2025-12-12T12:37:58.852Z
Learning: In tests under application-engine/src/test, GroupService.getDefaultSystemGroup() lazily creates the default system group if it doesn't exist. Tests can rely on this behavior and omit explicit existence/setup checks for the default system group.

Applied to files:

  • application-engine/src/test/groovy/com/netgrif/application/engine/export/service/ExportServiceTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/impersonation/ImpersonationServiceTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/TestHelper.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/ImporterTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/PetriNetTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/CachePetriNetServiceTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/workflow/NewInitTest.groovy
📚 Learning: 2025-12-12T12:40:25.863Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 397
File: application-engine/src/test/groovy/com/netgrif/application/engine/impersonation/ImpersonationServiceTest.groovy:18-20
Timestamp: 2025-12-12T12:40:25.863Z
Learning: In Groovy/Java, if a parameter or collection expects ActorFieldValue, you can pass UserFieldValue (a subclass) due to polymorphism. In tests, verify that ActorListFieldValue accepts ActorFieldValue instances and that passing UserFieldValue instances is valid without casts. This pattern applies to similar Groovy files across the repository.

Applied to files:

  • application-engine/src/test/groovy/com/netgrif/application/engine/export/service/ExportServiceTest.groovy
  • application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/impersonation/ImpersonationServiceTest.groovy
  • application-engine/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/TestHelper.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/ImporterTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/PetriNetTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/CachePetriNetServiceTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovy
  • application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/delegate/RoleActionDelegate.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/workflow/NewInitTest.groovy
  • application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/FieldActionsRunner.groovy
📚 Learning: 2025-09-29T10:31:57.325Z
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 362
File: application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/ProcessRoleService.java:513-529
Timestamp: 2025-09-29T10:31:57.325Z
Learning: PetriNet.getStringId() returns a simple ObjectId string representation (_id.toString()), not a composite Netgrif ID format, so new ObjectId(petriNetId) works correctly when petriNetId comes from PetriNet.getStringId().

Applied to files:

  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java
  • application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardManagementServiceImpl.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java
  • application-engine/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovy
  • application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
📚 Learning: 2025-09-29T10:31:31.469Z
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 362
File: application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/ProcessRoleController.java:43-45
Timestamp: 2025-09-29T10:31:31.469Z
Learning: In the Netgrif application engine, when Authentication objects reach controller endpoints, auth.getPrincipal() always returns a LoggedUser instance and is never null, so defensive casting checks are not necessary in controller methods.

Applied to files:

  • application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardManagementServiceImpl.java
  • application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.java
📚 Learning: 2025-11-14T10:22:01.634Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 383
File: application-engine/src/main/java/com/netgrif/application/engine/startup/ApplicationRunnerOrderResolver.java:43-43
Timestamp: 2025-11-14T10:22:01.634Z
Learning: For the netgrif/application-engine repository, avoid flagging trivial or nitpick-level issues such as redundant null checks, minor code style improvements, or obvious simplifications that don't affect functionality or introduce bugs. Focus review comments on substantive issues like logic errors, security concerns, performance problems, or breaking changes.

Applied to files:

  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
📚 Learning: 2025-07-29T17:19:18.300Z
Learnt from: tuplle
Repo: netgrif/application-engine PR: 331
File: application-engine/src/main/java/com/netgrif/application/engine/elastic/service/ElasticPetriNetService.java:45-46
Timestamp: 2025-07-29T17:19:18.300Z
Learning: In ElasticPetriNetService class, petriNetService is properly initialized using Lazy setter injection rather than constructor injection. This pattern with Lazy Autowired setter methods is commonly used in Spring to resolve circular dependencies and is a valid alternative to constructor injection.

Applied to files:

  • nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/petrinet/domain/PetriNet.java
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/PetriNet.java
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovy
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
📚 Learning: 2025-06-23T13:30:13.096Z
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 318
File: nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/menu/MenuItemConstants.java:60-62
Timestamp: 2025-06-23T13:30:13.096Z
Learning: In MenuItemConstants enum in nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/menu/MenuItemConstants.java, the field `attributeId` will be renamed to `value` to make it more generic and appropriate for both dataset attribute identifiers and technical constants like PATH_SEPARATOR.

Applied to files:

  • application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.java
🧬 Code graph analysis (3)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java (1)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java (1)
  • CacheMapKeys (3-7)
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (1)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java (1)
  • CacheMapKeys (3-7)
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (1)
nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/ActorTransformer.java (1)
  • ActorTransformer (10-131)
🪛 GitHub Actions: Pull Request
application-engine/src/main/java/com/netgrif/application/engine/importer/service/FunctionFactory.java

[error] 29-29: Compilation error: cannot find symbol 'GLOBAL' in Scope used by FunctionFactory.java.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
🔇 Additional comments (51)
nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/version/Version.java (2)

71-78: LGTM!

The isHigherThan convenience method correctly delegates to compareTo and improves code readability. The implementation and documentation are both accurate.


80-87: LGTM!

The isLowerThan convenience method correctly delegates to compareTo and provides a readable alternative to direct comparisons. The implementation and documentation are both accurate.

application-engine/src/test/groovy/com/netgrif/application/engine/TestHelper.groovy (1)

94-96: Clarify the need for both clearFunctionCache() and clearGlobalFunctionCache() in test cleanup.

These methods clear distinct caches: clearGlobalFunctionCache() clears global-scoped functions (keyed by petriNetId), while clearFunctionCache() clears individual cached functions. Verify both are necessary for proper test isolation, and if so, add a brief comment explaining their separate purposes.

application-engine/src/test/resources/petriNets/NAE-1290_Export_actions.xml (1)

220-220: No action needed—change is correct and aligns with codebase migration.

The method getNewestVersionByIdentifier has been removed project-wide. The change to getActiveVersionByIdentifier is the only available option and is already used throughout the test suite (ExportServiceTest.groovy, ImporterTest.groovy, and production code in WorkflowService, MenuImportExportService, FilterImportExportService). This change is necessary and correct.

nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/FunctionScope.java (2)

3-7: LGTM: Clear enum-level documentation.

The JavaDoc provides a concise description of the enum's purpose.


10-18: Deprecation handled: FunctionScope.NAMESPACE replaced with GLOBAL and automatically migrated.

The NAMESPACE enum constant has been replaced with GLOBAL in the FunctionScope enum. However, this change is managed through automatic deprecation handling, not an uncontrolled breaking change:

  1. Automatic migration: FunctionFactory.checkDeprecatedAttributes() (lines 26–30) automatically converts any imported function with scope="namespace" to GLOBAL and logs a deprecation warning.
  2. Test data compatibility: Existing test XML files (function_res.xml, function_test.xml, etc.) still use scope="namespace" and are transparently converted during import.
  3. Code migration: No remaining FunctionScope.NAMESPACE references in the codebase—already removed.

Minor improvements:

  • Add @deprecated annotation for explicit API visibility
  • Document this change in CHANGELOG for v7.0.0
  • Include migration timeline in deprecation warning

External consumers calling fromValue("namespace") directly (rather than importing XML) will receive IllegalArgumentException, which is expected behavior for removed enum values.

Likely an incorrect or invalid review comment.

application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/ImporterTest.groovy (1)

133-133: LGTM! Test updated to use active version API.

The test correctly uses the new getActiveVersionByIdentifier method to verify imported PetriNets, consistent with the API changes in this PR.

Also applies to: 207-207

application-engine/src/test/resources/change_caseref_value_action_test.xml (1)

32-32: LGTM! Test resource updated consistently.

The embedded scripts correctly use the new getActiveVersionByIdentifier method, maintaining consistency with the API changes.

Also applies to: 46-46

application-engine/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovy (1)

131-131: LGTM! Helper methods updated to use active version.

Both upsertNet and importProcessOnce correctly use getActiveVersionByIdentifier to check for existing PetriNets before creating new ones, which aligns with the intended semantics of these operations.

Also applies to: 252-252

application-engine/src/test/groovy/com/netgrif/application/engine/impersonation/ImpersonationServiceTest.groovy (1)

314-314: LGTM! Test updated to use active version API.

The test helper correctly uses getActiveVersionByIdentifier to retrieve the impersonation config PetriNet, consistent with the API changes. Note that this test is currently disabled.

application-engine/src/main/java/com/netgrif/application/engine/startup/runner/DefaultFiltersRunner.java (1)

427-427: LGTM! Filter creation updated to use active version.

The method correctly retrieves the active version of the "filter" PetriNet during filter case creation, which is appropriate for startup initialization logic.

application-engine/src/main/java/com/netgrif/application/engine/startup/runner/ImpersonationRunner.java (1)

42-42: The usage of getActiveVersionByIdentifier for impersonation configs is correct and consistent with the codebase pattern. The active version semantics are appropriate here—all similar system and configuration processes (dashboard management, filters, menu, preferences) consistently use this method to retrieve the currently deployed version. The test suite already expects this behavior, and there is no pattern of using "newest" versions for system configurations at runtime.

application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java (1)

153-166: Document the semantic difference between "active" and "latest" versions.

The API now provides both getActiveVersionByIdentifier (replacing getNewestVersionByIdentifier) and getLatestVersionByIdentifier. These methods should be documented to clarify:

  • What makes a version "active" vs "latest"
  • When callers should use one vs the other
  • Whether they can return different results, and if so, under what conditions

This distinction is important for correct usage throughout the codebase, particularly given the replacement of "newest" with "active."

application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java (1)

59-59: Breaking change: Configuration property structure refactored.

Renaming this field is part of a broader configuration structure migration. The external configuration changed from the old nae.field-runner.cache-size.* prefix structure (e.g., nae.field-runner.cache-size.namespace-functions) to the new netgrif.engine.runner.field-runner.* structure with renamed properties (e.g., netgrif.engine.runner.field-runner.globalFunctionsCacheSize). Existing configurations using the old property names will no longer work.

Likely an incorrect or invalid review comment.

application-engine/src/test/resources/petriNets/process_version_1_0_0.xml (1)

1-9: LGTM!

The XML structure is well-formed and correctly defines a test PetriNet resource for version 1.0.0, aligning with the active-version testing approach introduced in this PR.

application-engine/src/test/resources/petriNets/process_version_5_0_0.xml (1)

1-9: LGTM!

The XML structure is well-formed and correctly defines a test PetriNet resource for version 5.0.0.

application-engine/src/test/resources/petriNets/process_version_3_0_0.xml (1)

1-9: LGTM!

The XML structure is well-formed and correctly defines a test PetriNet resource for version 3.0.0.

application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FilterImportExportService.java (1)

269-272: Good defensive coding with the null check.

The addition of the null check for the active filter PetriNet prevents potential NPE in subsequent operations. The switch from newest to active version aligns with the PR's objectives.

application-engine/src/test/resources/petriNets/process_version_2_0_0.xml (1)

1-9: LGTM!

The XML structure is well-formed and correctly defines a test PetriNet resource for version 2.0.0.

nae-object-library/src/main/java/com/netgrif/application/engine/objects/elastic/domain/ElasticPetriNet.java (1)

27-27: LGTM!

The versionActive field is properly added, initialized in the constructor, and updated in the update method. This correctly mirrors the PetriNet's active-version state in the Elastic representation.

Also applies to: 43-43, 52-52

application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java (1)

288-288: Verify which version method was previously used for case creation.

The review comment references a change from getNewestVersionByIdentifier to getActiveVersionByIdentifier, but getNewestVersionByIdentifier does not exist in the codebase. The codebase contains getLatestVersionByIdentifier (which retrieves the highest version number) and getActiveVersionByIdentifier (which retrieves the version marked active). Clarify whether the change is from getLatestVersionByIdentifier or another method.

The underlying concern about semantic differences between active and latest versions remains valid: getActiveVersionByIdentifier returns versions with an active flag, while getLatestVersionByIdentifier returns versions sorted by version number. If the change is confirmed, verify that all callers (FilterImportExportService, MenuItemService, ActionApiImpl) are aware of this distinction and that test coverage includes scenarios where newest and active versions differ.

Likely an incorrect or invalid review comment.

application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/PetriNetTest.groovy (1)

139-139: LGTM: Improved error message clarity.

The updated exception message is more specific and actionable, including the process identifier and version number. This aligns with better error reporting practices.

application-engine/src/test/resources/petriNets/process_version_4_0_0.xml (1)

1-9: LGTM: Test resource completes version coverage.

This test resource follows the established pattern of other process version test files and fills the gap for version 4.0.0 in the test suite.

application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardManagementServiceImpl.java (1)

68-72: Good defensive null check; consistent with DashboardItemServiceImpl.

The defensive null check and error handling follow the same pattern as DashboardItemServiceImpl, which is good for consistency. The same verification regarding "active" vs "newest" version semantics applies here.

Ensure that the "active" version retrieval is appropriate for dashboard management case creation, consistent with the broader architectural decision reflected in DashboardItemServiceImpl and other services.

application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/repositories/PetriNetRepository.java (1)

36-43: LGTM: Well-documented repository method.

The new repository method follows Spring Data MongoDB naming conventions correctly and will be automatically implemented. The Javadoc is clear and properly formatted.

application-engine/src/test/groovy/com/netgrif/application/engine/export/service/ExportServiceTest.groovy (2)

131-131: LGTM: Test updated for active version semantics.

The test correctly uses getActiveVersionByIdentifier instead of getNewestVersionByIdentifier, aligning with the broader architectural changes in the codebase.


149-149: LGTM: Consistent version retrieval in test.

This change is consistent with line 131 and the broader test updates across the repository.

application-engine/src/test/groovy/com/netgrif/application/engine/workflow/NewInitTest.groovy (1)

48-48: LGTM: Test updated for active version semantics.

This change aligns with the consistent pattern across all test files, replacing getNewestVersionByIdentifier with getActiveVersionByIdentifier.

application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/FieldActionsRunner.groovy (1)

80-86: Cache method rename verified.

The method has been successfully renamed from getNamespaceFunctionCache() to getGlobalFunctionsCache(). The new method is defined in both the interface and implementation, and the usage in FieldActionsRunner.groovy is correct. No references to the old method name remain in the codebase.

application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.java (1)

66-70: Good defensive null check; pattern aligns with codebase standards.

The null check and clear exception message are good defensive programming. The use of getActiveVersionByIdentifier is consistent with the established pattern throughout the codebase—including similar services like DashboardManagementServiceImpl—for retrieving process versions when creating cases. No competing "newest version" method exists; the active version retrieval is the standard, intended approach used consistently across workflow operations.

application-engine/src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.java (1)

240-240: LGTM! Consistent migration to active version lookups.

The switch from getNewestVersionByIdentifier to getActiveVersionByIdentifier aligns with the broader refactoring across the codebase. Both call sites have appropriate null checks and error messages.

Also applies to: 264-264

application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy (1)

2447-2453: LGTM! Proper null guard and version lookup.

The migration to getActiveVersionByIdentifier with a null check and descriptive exception message is consistent with similar changes in RoleActionDelegate.groovy and across the codebase.

application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/delegate/RoleActionDelegate.groovy (1)

54-57: LGTM! Consistent active version lookup with proper null guards.

The migration to getActiveVersionByIdentifier with null checks and formatted error messages is consistent with the broader refactoring pattern across the codebase.

Also applies to: 81-84

application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/CachePetriNetServiceTest.groovy (1)

70-77: LGTM! Test updated to reflect active version semantics.

The test correctly uses getPetriNetActive() and getActiveVersionByIdentifier() to align with the refactored cache configuration and PetriNet service API.

nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/PetriNet.java (1)

76-79: LGTM! Well-structured active version flag implementation.

The versionActive field with @Indexed annotation, proper initialization in constructors, and helper methods (makeActive(), makeInactive()) provide a solid foundation for the active/latest version distinction introduced in this PR.

Also applies to: 166-166, 177-177, 441-447

application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IFieldActionsCacheService.java (1)

24-24: LGTM! Method renaming aligns with global cache terminology.

The renaming from getNamespaceFunctionCache() to getGlobalFunctionsCache() and clearNamespaceFunctionCache() to clearGlobalFunctionCache() is consistent with the broader cache refactoring (e.g., CacheMapKeys.FUNCTIONS, GenericMapCache) introduced in this PR. The new cacheAllPetriNetFunctions() method provides a clear entry point for bulk cache population.

Also applies to: 30-33

application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovy (2)

303-305: LGTM! Helper method standardizes PetriNet import calls.

The importProcess helper method centralizes the import logic and improves test readability. It's consistently used across existing and new tests.


150-212: LGTM! Comprehensive tests for active version behavior.

The new tests (testVersionsOnImport and testVersionActiveOnDelete) thoroughly validate:

  • Version activation on import
  • Version switching and state propagation to Elasticsearch
  • Error handling for invalid version imports
  • Version activation on delete (oldest version becomes active)

These tests provide solid coverage for the active version feature introduced in this PR.

Also applies to: 214-241

application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java (2)

29-52: LGTM! Cache naming changes align with the new active/latest versioning semantics.

The renaming from petriNetNewest to petriNetActive and the addition of petriNetLatest and globalFunctions cache names are consistent with the PR's objective to support distinct active vs. latest version caching.


66-71: Cache set construction is correct.

The getAllCaches() method properly includes all configured cache names in a LinkedHashSet to maintain order and avoid duplicates.

application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)

34-64: Per-key locking for compute-if-absent is well-implemented.

The double-checked locking pattern with per-key locks minimizes contention while ensuring thread-safe lazy loading. The finally block correctly cleans up the lock entry.

One minor note: the unchecked cast at lines 39, 47, and 57 could benefit from @SuppressWarnings("unchecked") on the method to suppress compiler warnings.

🔎 Optional: suppress unchecked warning
     @Override
+    @SuppressWarnings("unchecked")
     public <T> T get(Object key, Callable<T> loader) {
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java (2)

27-30: Good use of @RequiredArgsConstructor and migration to CachingConfigurer.

The switch from CachingConfigurerSupport (deprecated) to implementing CachingConfigurer directly is the correct modern approach.


42-58: Thread-safe bounded maps are correctly configured.

Using Collections.synchronizedMap(new MaxSizeHashMap<>(...)) addresses the thread-safety concern raised in previous reviews. The cache sizes are properly sourced from fieldRunnerProperties.

application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (2)

114-134: Good batched processing with error handling in cacheAllPetriNetFunctions.

The pagination approach prevents loading all PetriNets into memory at once, and the try-catch ensures one failing net doesn't abort the entire operation. The warning log provides good observability.


193-199: Well-implemented getRequiredCache helper.

This centralized null-check with a clear error message addresses previous review feedback and prevents NPEs throughout the service.

application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (6)

8-11: Good: Explicit imports instead of wildcard.

This addresses the previous review comment about avoiding * imports for consistency and preventing type shadowing.


140-154: Cache eviction correctly updated for new active/latest semantics.

Both evictAllCaches() and evictCache() now properly clear/evict from petriNetActive and petriNetLatest caches, consistent with the renamed cache configuration.


181-252: Comprehensive import flow with proper version management.

The @Transactional annotation ensures atomicity across the import operation. The version handling logic correctly:

  1. Prevents duplicate version imports
  2. Auto-increments version when not specified
  3. Rejects lower versions than existing latest
  4. Maintains single active version invariant by inactivating previous

One consideration: the inactivatedProcess is saved before newProcess (lines 243-246). If doSaveInternal(newProcess) fails after doSaveInternal(inactivatedProcess) succeeds, the transaction rollback should revert both—verify that the MongoDB transaction configuration supports this.


254-271: Good refactoring: doSaveInternal consolidates save logic.

Extracting the common save operations (initialize arcs, evict cache, persist, index) into a protected method improves maintainability and ensures consistency.


310-331: New caching methods properly guard against null identifiers.

Both getActiveVersionByIdentifier and getLatestVersionByIdentifier include null checks and use appropriate @Cacheable annotations with unless = "#result == null" to avoid caching null results.

The getLatestVersionByIdentifier implementation correctly queries with descending sort on version components to find the highest version.


596-604: Correct fallback: Activates latest version when active version is deleted.

This maintains the invariant that there should always be an active version (if any versions exist) by promoting the latest version when the currently active one is deleted.

petriNets = petriNetService.getAll(Pageable.unpaged()).getContent();
} else {
petriNets = processes.stream().map(process -> petriNetService.getNewestVersionByIdentifier(process)).collect(Collectors.toList());
petriNets = processes.stream().map(process -> petriNetService.getActiveVersionByIdentifier(process)).collect(Collectors.toList());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Potential NullPointerException from null PetriNet values.

The getActiveVersionByIdentifier call within the stream can return null if a process identifier has no active version. These null values will be collected into the petriNets list, and the subsequent isEmpty() check at line 285 won't catch them. When the code iterates over petriNets at line 302, calling net.getImmediateFields() will throw a NullPointerException.

This could be the source of the test errors reported by Retoocs.

🔎 Apply this diff to filter out null values:
         } else {
-            petriNets = processes.stream().map(process -> petriNetService.getActiveVersionByIdentifier(process)).collect(Collectors.toList());
+            petriNets = processes.stream()
+                    .map(process -> petriNetService.getActiveVersionByIdentifier(process))
+                    .filter(Objects::nonNull)
+                    .collect(Collectors.toList());
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
petriNets = processes.stream().map(process -> petriNetService.getActiveVersionByIdentifier(process)).collect(Collectors.toList());
petriNets = processes.stream()
.map(process -> petriNetService.getActiveVersionByIdentifier(process))
.filter(Objects::nonNull)
.collect(Collectors.toList());
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java
around line 283, the stream maps processes to petriNets using
petriNetService.getActiveVersionByIdentifier which can return null, causing
NullPointerExceptions later when iterating and calling net.getImmediateFields();
filter out null results after the map (e.g., add a filter(Objects::nonNull) or
equivalent) before collecting into petriNets, or alternatively replace nulls
with a safe placeholder and handle them explicitly; ensure subsequent checks
consider an empty list after filtering so iteration over petriNets never
encounters null entries.

PetriNet filterNet = petriNetService.getNewestVersionByIdentifier(FILTER_NET_IDENTIFIER);
PetriNet filterNet = petriNetService.getActiveVersionByIdentifier(FILTER_NET_IDENTIFIER);
if (filterNet == null) {
throw new IllegalStateException("No filter process found or active");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider clarifying the error message.

The message "No filter process found or active" could be more explicit about whether the process doesn't exist or simply isn't active.

🔎 Apply this diff to clarify:
-                throw new IllegalStateException("No filter process found or active");
+                throw new IllegalStateException("No active filter process with identifier '" + FILTER_NET_IDENTIFIER + "' found");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
throw new IllegalStateException("No filter process found or active");
throw new IllegalStateException("No active filter process with identifier '" + FILTER_NET_IDENTIFIER + "' found");
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FilterImportExportService.java
around line 271, improve the thrown exception message so it clearly
distinguishes between a missing filter process and an inactive one; change the
message to state "Filter process not found or not active" more explicitly and,
if available in scope, append identifying details (e.g., filter/process id or
name) and the state (missing vs. inactive) so the exception reads like "Filter
process '[id/name]' not found" or "Filter process '[id/name]' found but not
active" depending on the condition.

Comment on lines 289 to 291
if (net == null) {
throw new IllegalArgumentException("Petri net with identifier [" + identifier + "] does not exist.");
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider clarifying the error message.

The error message states "does not exist" but with the change to active-version lookup, the issue could be that the PetriNet exists but is not active. Consider making the error message more specific.

🔎 Apply this diff to clarify the error message:
-            throw new IllegalArgumentException("Petri net with identifier [" + identifier + "] does not exist.");
+            throw new IllegalArgumentException("Active Petri net with identifier [" + identifier + "] not found.");

Also applies to: 303-305, 312-314

🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java
around lines 289-291 (also update similar checks at 303-305 and 312-314), the
IllegalArgumentException message "Petri net with identifier [...] does not
exist." is ambiguous after switching to active-version lookup; change the
message to distinguish between "does not exist" and "exists but no active
version found." Update each thrown exception to indicate that either the Petri
net was not found or that an active version is not available (e.g., "Petri net
with identifier [X] was found but no active version exists" or "Petri net with
identifier [X] does not exist or has no active version") so the error clearly
reflects the active-version lookup outcome.

Comment on lines +55 to +60
/**
* Compares this version to the other version
*
* @param other other version to be compared with
* @return 0 if the versions equal, <0 if this is lower than other, >0 if this is higher than other</0>
*/
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix malformed HTML tag in JavaDoc.

Line 59 contains </0> which is invalid HTML. Remove the closing tag.

🔎 Apply this diff to fix the JavaDoc:
     /**
      * Compares this version to the other version
      *
      * @param other other version to be compared with
-     * @return 0 if the versions equal, <0 if this is lower than other, >0 if this is higher than other</0>
+     * @return 0 if the versions equal, <0 if this is lower than other, >0 if this is higher than other
      */
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Compares this version to the other version
*
* @param other other version to be compared with
* @return 0 if the versions equal, <0 if this is lower than other, >0 if this is higher than other</0>
*/
/**
* Compares this version to the other version
*
* @param other other version to be compared with
* @return 0 if the versions equal, <0 if this is lower than other, >0 if this is higher than other
*/
🤖 Prompt for AI Agents
In
nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/version/Version.java
around lines 55 to 60, the JavaDoc return description contains a malformed HTML
closing tag `</0>` on line 59; remove that invalid tag and replace it with the
intended plain text punctuation (e.g., a period) so the @return line reads
correctly: "0 if the versions equal, <0 if this is lower than other, >0 if this
is higher than other."

renczesstefan and others added 19 commits January 7, 2026 10:29
Introduce `FunctionValidator` to centralize and streamline deprecated attribute handling. Enhance PetriNet service with `getAllActive` to retrieve only active PetriNets, and update relevant logic and documentation for consistency. Improve clarity in cache-related configurations and rename caching terms for better alignment with global functions.
- refactor version initialization and validation logic on process import
- partly enable and update PetriNetServiceTest
- rename keyword active to default
- use ResourceNotFoundException
- add paged result in PetriNetRepository.findByIdentifierAndDefaultVersion
- fix outcome consistency in PetriNetService
# Conflicts:
#	application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java
#	application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
- defined more specific documentation on globalFunctions property
- added JavaDocs to FunctionValidator.checkDeprecatedAttributes
- added functionCachingPageSize to configuration properties
Introduce a guide covering Petriflow functions, their scopes, usage, and examples in Groovy Closure style. Clarifies function declaration, behavior, method overloading, and runtime compilation details.
Corrected spelling errors and clarified some sentences for better readability. Added missing newline formatting to improve document structure.
Replaced unsynchronized `MaxSizeHashMap` with `Collections.synchronizedMap` to ensure thread-safe access to action, global function, and function caches. This prevents potential concurrency issues in a multi-threaded environment.
# Conflicts:
#	application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java
#	application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
Replaced the method call for retrieving the active version of a Petri net with a call to fetch the default version. This ensures consistency in caching behavior by aligning it with the default Petri net configuration.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java (2)

301-307: Same breaking change: Verify default version availability.

Line 302 also switches to getDefaultVersionByIdentifier. The null check at line 303 is present, but ensure all process identifiers have a default version designated before deployment.


310-316: Same breaking change: Verify default version availability.

Line 311 also switches to getDefaultVersionByIdentifier. The null check at line 313 is present, but ensure all process identifiers have a default version designated before deployment.

application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java (1)

267-286: Add null filter to stream to prevent NullPointerException at line 302.

Line 283 calls getDefaultVersionByIdentifier without null handling. Since this method can return null when no default version exists (see PetriNetService line 340-350), the list may contain null values. At line 302, net.getImmediateFields() will throw NullPointerException if net is null.

Additionally, this change switches from retrieving the newest version to the default version, which alters search behavior. Default and newest versions may have different fields, causing search results to differ.

Apply the suggested filter:

Diff
-petriNets = processes.stream().map(process -> petriNetService.getDefaultVersionByIdentifier(process)).collect(Collectors.toList());
+petriNets = processes.stream()
+    .map(process -> petriNetService.getDefaultVersionByIdentifier(process))
+    .filter(Objects::nonNull)
+    .collect(Collectors.toList());
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.java (1)

263-268: Missing null check - potential NullPointerException.

Unlike the first call site (line 240) and the pattern established in DashboardItemServiceImpl and DashboardManagementServiceImpl, this call to getDefaultVersionByIdentifier("preference_filter_item") directly dereferences the result without a null check. If the preference_filter_item process is not deployed, this will throw an NPE instead of a meaningful error.

Proposed fix: Add null check with error handling
         //Creating new Case of preference_filter_item net and setting its data...
+        PetriNet preferenceFilterNet = petriNetService.getDefaultVersionByIdentifier("preference_filter_item");
+        if (preferenceFilterNet == null) {
+            resultMessage.append("- preference_filter_item process not found! Menu entry was skipped.\n");
+            return "";
+        }
         Case menuItemCase = workflowService.createCase(
-                petriNetService.getDefaultVersionByIdentifier("preference_filter_item").getStringId(),
+                preferenceFilterNet.getStringId(),
                 item.getEntryName() + "_" + menuIdentifier,
                 "",
                 ActorTransformer.toLoggedUser(userService.getSystem())
         ).getCase();
🤖 Fix all issues with AI agents
In
@application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java:
- Line 4: Replace the wildcard import
"com.netgrif.application.engine.objects.petrinet.domain.*" with explicit imports
for each concrete class referenced in IPetriNetService; update the import list
to only include the exact types used by the interface (for example PetriNet,
PetriNetDraft, PetriNetVersion, Place, Transition, Arc — or whatever specific
domain classes the file actually references) so the file no longer uses a star
import and only imports the concrete symbols referenced by the interface.

In
@application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java:
- Around line 246-280: The conditional branches in checkAndHandleProcessVersion
are dense and need brief inline comments to explain each scenario; add short
comments near the blocks that handle: (a) initial import when
existingLatestProcess==null and newProcess.getVersion()==null (initialize new
Version), (b) version auto-increment when newProcess.getVersion()==null (clone
and increment from existingLatestProcess), (c) branch where a provided
newProcess.getVersion() is lower than existingLatestProcess.getVersion() (set
makeNonDefaultCurrentVersion=false), and (d) the two paths that demote the
current default (when existingLatestProcess.isDefaultVersion() vs when falling
back to self.getDefaultVersionByIdentifier) and finally the
newProcess.makeDefault() call; place comments adjacent to variables/conditions
like newProcess, existingLatestProcess, makeNonDefaultCurrentVersion, and
processToMakeNonDefault so future readers can quickly map each conditional to
the scenario described in the PR comment.

In
@application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java:
- Around line 97-112: The getCachedFunctions method can NPE when
functionsCache.get(...).get() is called if the native cache isn't a Map and the
entry is missing; replace the current instanceof Map block and manual
containsKey logic in getCachedFunctions with the Cache.get(key, Callable)
pattern to atomically load and store values (use CachedFunction.build(shell,
function) as the loader), then cast the returned value to CachedFunction and add
it to cachedFunctions; ensure you remove reliance on nativeFunctionsCache and
map.containsKey to avoid the null dereference.
- Around line 82-95: getCompiledAction currently assumes
actionsCache.get(stringId) is non-null and that getNativeCache() is a Map; if
nativeActionsCache isn't a Map the population step is skipped and
actionsCache.get(stringId) may be null causing an NPE. Fix by making
getCompiledAction robust: always check actionsCache.get(stringId) for null
before calling .get(), and if missing compute the Closure via
shell.evaluate("{-> " + action.getDefinition() + "}") and put it into
actionsCache (even when nativeActionsCache is not a Map) so caching still works;
reference getCompiledAction, actionsCache, nativeActionsCache,
CacheMapKeys.ACTIONS and shell.evaluate when applying the change.

In
@application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovy:
- Around line 151-217: The test testVersionsOnImport uses three
Thread.sleep(5000) calls which are slow and flaky; replace them with a
polling/await strategy that waits until the expected ElasticPetriNet state is
reached (use elasticPetriNetRepository.findById(petriNetX.stringId) and assert
conditions inside the waiter). Implement or reuse a helper like
waitForCondition/awaitility to poll until the Optional is present and its
isDefaultVersion()/defaultVersion matches the expected boolean (with a
reasonable timeout), and swap each Thread.sleep(5000) with calls to that helper;
reference the testVersionsOnImport method and the
elasticPetriNetRepository.findById checks to locate where to change.

In
@nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/PetriNet.java:
- Around line 441-447: The public mutators makeDefault() and makeNonDefault() on
PetriNet currently allow callers to mark a version default without coordinating
with other versions; change their visibility to package-private (remove public)
and/or add validation logic inside makeDefault() to delegate to or call a
service (e.g., a VersionManagementService) that enforces the “single default per
identifier” rule, or have makeDefault() clear other defaults for the same
identifier by invoking that service; ensure references to
setDefaultVersion(boolean) remain internal and that all external code paths
route through the service-layer API that enforces the constraint.
- Around line 76-79: Add a DB-level unique compound index to the PetriNet entity
to ensure only one default per identifier: update the PetriNet class (where the
field defaultVersion is declared) to declare a unique compound index on
(identifier, defaultVersion) and prefer a partial/filtered unique index where
defaultVersion is true (so only one document with defaultVersion==true per
identifier is allowed). Use the domain's index annotation mechanism (e.g.,
@CompoundIndex with def = "{'identifier': 1, 'defaultVersion': 1}", unique =
true and a partialFilter/partialExpression for {'defaultVersion': true} if
supported) or add an equivalent DB migration to create the unique partial index
at the database level.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f7b61be and 749db25.

📒 Files selected for processing (27)
  • application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy
  • application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/delegate/RoleActionDelegate.groovy
  • application-engine/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovy
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java
  • application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.java
  • application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardManagementServiceImpl.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/repositories/PetriNetRepository.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java
  • application-engine/src/main/java/com/netgrif/application/engine/startup/runner/DefaultFiltersRunner.java
  • application-engine/src/main/java/com/netgrif/application/engine/startup/runner/ImpersonationRunner.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FilterImportExportService.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java
  • application-engine/src/test/groovy/com/netgrif/application/engine/export/service/ExportServiceTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/impersonation/ImpersonationServiceTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/ImporterTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/CachePetriNetServiceTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/workflow/NewInitTest.groovy
  • application-engine/src/test/resources/change_caseref_value_action_test.xml
  • application-engine/src/test/resources/petriNets/NAE-1290_Export_actions.xml
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/common/ResourceNotFoundExceptionCode.java
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/elastic/domain/ElasticPetriNet.java
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/PetriNet.java
🧰 Additional context used
🧠 Learnings (15)
📚 Learning: 2025-07-31T23:40:46.499Z
Learnt from: tuplle
Repo: netgrif/application-engine PR: 334
File: application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java:204-214
Timestamp: 2025-07-31T23:40:46.499Z
Learning: In the PetriNetService.importPetriNet method, existingNet.getVersion() cannot be null because all existing nets in the system were deployed through processes that ensure every net always has a version assigned.

Applied to files:

  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java
  • application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/delegate/RoleActionDelegate.groovy
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/elastic/domain/ElasticPetriNet.java
  • application-engine/src/test/resources/change_caseref_value_action_test.xml
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java
  • application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.java
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/CachePetriNetServiceTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/ImporterTest.groovy
  • application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardManagementServiceImpl.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FilterImportExportService.java
  • application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovy
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.java
  • application-engine/src/main/java/com/netgrif/application/engine/startup/runner/DefaultFiltersRunner.java
  • application-engine/src/main/java/com/netgrif/application/engine/startup/runner/ImpersonationRunner.java
  • application-engine/src/test/groovy/com/netgrif/application/engine/workflow/NewInitTest.groovy
  • application-engine/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovy
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/PetriNet.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java
📚 Learning: 2025-12-12T13:40:00.786Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 397
File: application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java:98-104
Timestamp: 2025-12-12T13:40:00.786Z
Learning: In application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java, the permission constraint logic in buildQuery() intentionally applies negative actor constraints globally: ((roleConstraint AND NOT negRoleConstraint) OR actorConstraint) AND NOT negActorConstraint. This means users in negativeViewActors are excluded even if they have positive role permissions, which is the intended authorization behavior.

Applied to files:

  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java
  • application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.java
  • application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardManagementServiceImpl.java
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovy
📚 Learning: 2025-12-19T07:55:13.427Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 400
File: application-engine/src/main/java/com/netgrif/application/engine/workflow/web/AbstractTaskController.java:239-247
Timestamp: 2025-12-19T07:55:13.427Z
Learning: In the netgrif/application-engine project, the `taskService.findOne()` method (from ITaskService) never returns null. When a task is not found, it handles the error through other means (such as throwing an exception) rather than returning null, so null checks after `findOne()` calls are unnecessary.

Applied to files:

  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java
📚 Learning: 2025-10-20T11:46:37.958Z
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 373
File: application-engine/src/main/java/com/netgrif/application/engine/actions/ActionApiImpl.java:105-107
Timestamp: 2025-10-20T11:46:37.958Z
Learning: In ActionApiImpl, the processIdentifier parameter in searchCases() is intentionally unused because it's required by the ActionApi interface for plugin implementations. This implementation returns cases from all processes without filtering by processIdentifier.

Applied to files:

  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java
📚 Learning: 2025-12-19T09:06:38.316Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 398
File: application-engine/src/main/java/com/netgrif/application/engine/importer/service/Importer.java:57-58
Timestamp: 2025-12-19T09:06:38.316Z
Learning: In the file `application-engine/src/main/java/com/netgrif/application/engine/importer/service/Importer.java`, the `definition` attribute of the `Action` class (from `com.netgrif.application.engine.objects.petrinet.domain.dataset.logic.action.Action`) is never null, so null checks for `action.getDefinition()` are not needed.

Applied to files:

  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java
  • application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/delegate/RoleActionDelegate.groovy
  • application-engine/src/test/resources/change_caseref_value_action_test.xml
  • application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.java
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/ImporterTest.groovy
  • application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardManagementServiceImpl.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FilterImportExportService.java
  • application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.java
  • application-engine/src/main/java/com/netgrif/application/engine/startup/runner/DefaultFiltersRunner.java
  • application-engine/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovy
  • application-engine/src/test/resources/petriNets/NAE-1290_Export_actions.xml
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java
📚 Learning: 2025-12-12T12:36:06.651Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 397
File: application-engine/src/main/resources/petriNets/engine-processes/org_group.xml:36-39
Timestamp: 2025-12-12T12:36:06.651Z
Learning: In the org_group.xml file (application-engine/src/main/resources/petriNets/engine-processes/org_group.xml), variable names in action blocks like userField and user_selection should be kept as-is even when the field type changes from "user" to "actor".

Applied to files:

  • application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/delegate/RoleActionDelegate.groovy
  • application-engine/src/test/resources/change_caseref_value_action_test.xml
  • application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.java
  • application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardManagementServiceImpl.java
  • application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy
  • application-engine/src/test/resources/petriNets/NAE-1290_Export_actions.xml
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
📚 Learning: 2025-12-12T12:40:25.863Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 397
File: application-engine/src/test/groovy/com/netgrif/application/engine/impersonation/ImpersonationServiceTest.groovy:18-20
Timestamp: 2025-12-12T12:40:25.863Z
Learning: In Groovy/Java, if a parameter or collection expects ActorFieldValue, you can pass UserFieldValue (a subclass) due to polymorphism. In tests, verify that ActorListFieldValue accepts ActorFieldValue instances and that passing UserFieldValue instances is valid without casts. This pattern applies to similar Groovy files across the repository.

Applied to files:

  • application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/delegate/RoleActionDelegate.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/CachePetriNetServiceTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/ImporterTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/export/service/ExportServiceTest.groovy
  • application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/workflow/NewInitTest.groovy
  • application-engine/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/impersonation/ImpersonationServiceTest.groovy
📚 Learning: 2025-06-23T13:30:13.096Z
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 318
File: nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/menu/MenuItemConstants.java:60-62
Timestamp: 2025-06-23T13:30:13.096Z
Learning: In MenuItemConstants enum in nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/menu/MenuItemConstants.java, the field `attributeId` will be renamed to `value` to make it more generic and appropriate for both dataset attribute identifiers and technical constants like PATH_SEPARATOR.

Applied to files:

  • application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.java
📚 Learning: 2025-09-29T10:31:31.469Z
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 362
File: application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/ProcessRoleController.java:43-45
Timestamp: 2025-09-29T10:31:31.469Z
Learning: In the Netgrif application engine, when Authentication objects reach controller endpoints, auth.getPrincipal() always returns a LoggedUser instance and is never null, so defensive casting checks are not necessary in controller methods.

Applied to files:

  • application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.java
  • application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardManagementServiceImpl.java
📚 Learning: 2025-12-12T12:37:58.852Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 397
File: application-engine/src/test/groovy/com/netgrif/application/engine/auth/WorkflowAuthorizationServiceTest.groovy:4-4
Timestamp: 2025-12-12T12:37:58.852Z
Learning: In tests under application-engine/src/test, GroupService.getDefaultSystemGroup() lazily creates the default system group if it doesn't exist. Tests can rely on this behavior and omit explicit existence/setup checks for the default system group.

Applied to files:

  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/CachePetriNetServiceTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/ImporterTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/export/service/ExportServiceTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/workflow/NewInitTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/impersonation/ImpersonationServiceTest.groovy
📚 Learning: 2025-09-05T10:21:54.893Z
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 350
File: application-engine/src/test/groovy/com/netgrif/application/engine/export/service/ExportServiceTest.groovy:135-135
Timestamp: 2025-09-05T10:21:54.893Z
Learning: In ExportServiceTest.groovy, writing to src/test/resources is intentional to simulate production behavior where the working tree is mutated during file exports. This mirrors how the system works in production.

Applied to files:

  • application-engine/src/test/groovy/com/netgrif/application/engine/export/service/ExportServiceTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovy
  • application-engine/src/test/resources/petriNets/NAE-1290_Export_actions.xml
📚 Learning: 2025-07-29T17:19:18.300Z
Learnt from: tuplle
Repo: netgrif/application-engine PR: 331
File: application-engine/src/main/java/com/netgrif/application/engine/elastic/service/ElasticPetriNetService.java:45-46
Timestamp: 2025-07-29T17:19:18.300Z
Learning: In ElasticPetriNetService class, petriNetService is properly initialized using Lazy setter injection rather than constructor injection. This pattern with Lazy Autowired setter methods is commonly used in Spring to resolve circular dependencies and is a valid alternative to constructor injection.

Applied to files:

  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovy
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
📚 Learning: 2026-01-08T08:37:58.317Z
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 363
File: application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java:66-69
Timestamp: 2026-01-08T08:37:58.317Z
Learning: In the CacheConfigurationProperties class, the cache property for namespace/global functions is named `globalFunctions`, not `namespaceFunctions`. This property was renamed as part of the refactoring from namespace-scoped to global-scoped function caching.

Applied to files:

  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java
📚 Learning: 2025-09-29T10:31:57.325Z
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 362
File: application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/ProcessRoleService.java:513-529
Timestamp: 2025-09-29T10:31:57.325Z
Learning: PetriNet.getStringId() returns a simple ObjectId string representation (_id.toString()), not a composite Netgrif ID format, so new ObjectId(petriNetId) works correctly when petriNetId comes from PetriNet.getStringId().

Applied to files:

  • application-engine/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovy
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java
📚 Learning: 2025-11-14T10:22:01.634Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 383
File: application-engine/src/main/java/com/netgrif/application/engine/startup/ApplicationRunnerOrderResolver.java:43-43
Timestamp: 2025-11-14T10:22:01.634Z
Learning: For the netgrif/application-engine repository, avoid flagging trivial or nitpick-level issues such as redundant null checks, minor code style improvements, or obvious simplifications that don't affect functionality or introduce bugs. Focus review comments on substantive issues like logic errors, security concerns, performance problems, or breaking changes.

Applied to files:

  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java
🧬 Code graph analysis (2)
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (2)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java (1)
  • CacheMapKeys (3-7)
application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)
  • Slf4j (13-111)
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (1)
nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/ActorTransformer.java (1)
  • ActorTransformer (10-131)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
🔇 Additional comments (37)
application-engine/src/test/groovy/com/netgrif/application/engine/export/service/ExportServiceTest.groovy (1)

131-131: The concern about a version mismatch is incorrect. When a PetriNet is created via importHelper.createNet(fileName, VersionType.MAJOR), the importPetriNet method automatically calls makeDefault() on the newly imported process (PetriNetService.java, lines 275-276). This ensures that the newly created test PetriNet with identifier "export_test" is set as the default version and will be retrieved by getDefaultVersionByIdentifier("export_test") at lines 131 and 149. The processId retrieved will match testNet.stringId, so the task queries will find the expected results. No fix is needed.

application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/ImporterTest.groovy (1)

133-133: LGTM - Test updated to use renamed API method.

The test correctly uses getDefaultVersionByIdentifier instead of the deprecated getNewestVersionByIdentifier, aligning with the API refactoring that introduces default/active version semantics.

Also applies to: 207-207

application-engine/src/test/groovy/com/netgrif/application/engine/workflow/NewInitTest.groovy (1)

48-48: LGTM - Correct usage of renamed method.

The test properly uses getDefaultVersionByIdentifier to retrieve the PetriNet for case creation, consistent with the API refactoring.

application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/repositories/PetriNetRepository.java (1)

36-44: LGTM - Well-defined repository query method.

The new findByIdentifierAndDefaultVersion method follows Spring Data naming conventions and properly supports the default version feature with pagination. The Javadoc clearly documents the method's behavior.

application-engine/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovy (1)

131-131: LGTM - Import helper updated consistently.

Both upsertNet and importProcessOnce methods now correctly use getDefaultVersionByIdentifier to check for existing PetriNets, aligning with the API refactoring.

Also applies to: 252-252

application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java (1)

153-166: Semantic distinction between "default" and "latest" versions is correctly implemented.

The refactoring properly replaces getNewestVersionByIdentifier with two semantically distinct methods:

  • getDefaultVersionByIdentifier (line 345): Queries via findByIdentifierAndDefaultVersion(identifier, true, ...) to retrieve the version marked as the active/designated version by a boolean flag.
  • getLatestVersionByIdentifier (lines 359-360): Queries via findByIdentifier sorted descending by semantic version (major.minor.patch) to retrieve the most recent version regardless of default status.

No remaining references to the old method name exist. Usage patterns across FieldActionsCacheService, WorkflowService, MenuImportExportService, FilterImportExportService, CaseSearchService, and startup runners confirm that cases previously relying on "newest" behavior now correctly use "default" for business logic operations, while "latest" is reserved for internal version management within PetriNetService (e.g., when automatically promoting the latest to default after deletion).

nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/PetriNet.java (1)

143-167: Good practice: Explicit initialization of defaultVersion.

Initializing defaultVersion via makeNonDefault() ensures all new PetriNet instances consistently start in a non-default state, preventing accidental default designation.

nae-object-library/src/main/java/com/netgrif/application/engine/objects/elastic/domain/ElasticPetriNet.java (1)

27-27: LGTM: ElasticPetriNet correctly mirrors defaultVersion field.

The defaultVersion field is properly initialized in the constructor and updated in the update() method, maintaining consistency with the source PetriNet object.

Also applies to: 39-48, 50-58

nae-object-library/src/main/java/com/netgrif/application/engine/objects/common/ResourceNotFoundExceptionCode.java (1)

9-10: LGTM: New exception code follows established pattern.

The DEFAULT_PROCESS_NOT_FOUND enum constant is properly added to support error handling for missing default process versions.

application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java (1)

287-293: Incorrect method name in review comment.

The review references getNewestVersionByIdentifier, which does not exist in the codebase. The actual change is from getLatestVersionByIdentifier to getDefaultVersionByIdentifier.

This changes which PetriNet version is used when creating cases by identifier. However, the framework design ensures a default version is always maintained: when new processes are imported, they are automatically marked as default, and when the default is deleted, the latest version is automatically promoted to default. The null check at line 289 is appropriate for robustness despite these guarantees.

application-engine/src/test/resources/petriNets/NAE-1290_Export_actions.xml (1)

220-220: LGTM - API change aligned with default version semantics.

The change from getNewestVersionByIdentifier to getDefaultVersionByIdentifier is consistent with the PR's objective to standardize on default version lookups. In a test resource context, if the net is missing, the resulting NPE will appropriately fail the test.

application-engine/src/main/java/com/netgrif/application/engine/startup/runner/ImpersonationRunner.java (1)

41-46: LGTM - Null handling already in place.

The API migration to getDefaultVersionByIdentifier is correct, and the existing null check on line 43 properly handles the case where no default version exists, proceeding to create the net via the helper.

application-engine/src/main/java/com/netgrif/application/engine/startup/runner/DefaultFiltersRunner.java (1)

425-428: LGTM - Graceful null handling preserved.

The migration to getDefaultVersionByIdentifier maintains the existing graceful degradation pattern - returning Optional.empty() when the filter process doesn't exist, allowing callers to handle the absence appropriately.

application-engine/src/test/groovy/com/netgrif/application/engine/impersonation/ImpersonationServiceTest.groovy (1)

314-314: LGTM - Test helper aligned with API change.

The API migration in the test helper is consistent with the PR's standardization on default version lookups. Since this is test code (and the test class is disabled pending impersonation rework), a missing net would appropriately fail the test setup.

application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardManagementServiceImpl.java (1)

70-74: LGTM - Proper null handling with explicit exception.

The migration to getDefaultVersionByIdentifier with an explicit null check and ResourceNotFoundException is the correct pattern for production service code. This provides clear error messaging when the dashboard management process is not configured.

application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.java (1)

68-72: LGTM - Consistent null handling pattern.

The implementation mirrors DashboardManagementServiceImpl with proper null checking and exception handling. This ensures consistent behavior across dashboard-related services when the required process is not found.

application-engine/src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.java (1)

240-258: LGTM - Null handling with descriptive messaging.

The API migration properly leverages the existing null check, appending a meaningful message to the result when the referenced net is not found.

application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/delegate/RoleActionDelegate.groovy (2)

53-59: LGTM: Default version lookup with proper error handling.

The change from getNewestVersionByIdentifier to getDefaultVersionByIdentifier aligns with the project-wide version semantics refactoring. The null check with a clear exception message ensures robust error handling when a process cannot be found.


80-86: LGTM: Consistent error handling pattern.

The implementation mirrors the null-check pattern in assignRole (lines 53-59), ensuring consistent behavior across role manipulation operations.

application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy (1)

2446-2453: LGTM: Proper validation in role collection.

The change to getDefaultVersionByIdentifier with null validation is appropriate. The exception message clearly indicates the context ("when collecting roles"), which aids debugging. Note that this exception will propagate out of the collectEntries closure, which is the intended behavior.

application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FilterImportExportService.java (1)

271-274: LGTM: Appropriate use of domain-specific exception.

The change to getDefaultVersionByIdentifier with null validation is correct. Using ResourceNotFoundException with a specific exception code (DEFAULT_PROCESS_NOT_FOUND) is appropriate for the service layer and provides better error categorization than the IllegalArgumentException used in the Groovy action delegates.

application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/CachePetriNetServiceTest.groovy (1)

69-78: LGTM: Test correctly updated for default version caching.

The test properly validates the caching behavior for getDefaultVersionByIdentifier. All three cache assertions consistently reference getPetriNetDefault(), confirming that:

  1. The cache key has been updated from "newest" to "default"
  2. Lazy caching behavior is preserved
  3. The cached value matches the retrieved value
application-engine/src/test/resources/change_caseref_value_action_test.xml (2)

32-32: LGTM: Test resource updated to use default version API.

The change from getNewestVersionByIdentifier to getDefaultVersionByIdentifier correctly aligns this test resource with the refactored API.


46-46: LGTM: Consistent API usage in test scenario.

This test scenario for handling invalid nets now uses the default version lookup, consistent with line 32 and the broader API refactoring.

application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java (2)

29-52: LGTM! Cache properties correctly align with the new default/latest version semantics.

The new petriNetDefault, petriNetLatest, and globalFunctions cache names are well-documented and consistent with the broader caching refactor. Based on learnings, the globalFunctions property name is correct (renamed from namespace-scoped to global-scoped function caching).


66-71: LGTM! getAllCaches() correctly includes all configured cache names.

The method properly returns all cache names including the new petriNetDefault, petriNetLatest, and globalFunctions caches.

application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovy (4)

99-101: Verify why processImportAndDelete test is disabled.

This test was working before and is now marked with @Disabled. The PR comment mentions "There are test errors." If this test is disabled to work around failures, consider fixing the underlying issues rather than disabling the test.


219-246: LGTM! Good test coverage for default version reassignment on delete.

The test correctly verifies that when the default version is deleted, the latest remaining version becomes the new default. This aligns with the logic in PetriNetService.deletePetriNet().


248-250: Verify why processSearch test is disabled.

Similar to processImportAndDelete, this test is newly disabled. Confirm this is intentional and not masking test failures from the changes in this PR.


309-311: LGTM! Good refactor extracting the import helper method.

The helper method improves readability and reduces code duplication across tests.

application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (2)

114-134: LGTM! Good batch caching with error handling.

The cacheAllPetriNetFunctions method correctly paginates through all PetriNets and handles individual failures gracefully without stopping the entire batch. The error logging is appropriate.


193-199: LGTM! Good defensive helper method.

The getRequiredCache method provides a clear error message when a cache is not configured, which aids debugging.

application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (5)

140-154: LGTM! Cache eviction updated correctly for new cache names.

The evictAllCaches and evictCache methods properly handle the new petriNetDefault and petriNetLatest caches.


180-224: LGTM! Import flow correctly handles version transitions with @Transactional.

The @Transactional annotation ensures atomicity when saving both the process being made non-default and the new process. The order of operations (save processToMakeNonDefault before newProcess) correctly ensures there's never a state with two default versions.


314-324: Good defensive null check added.

The null guards for identifier and version prevent cache key issues and NPEs. The unless = "#result == null" in @Cacheable correctly avoids caching null results.


338-365: LGTM! New default/latest version retrieval methods are well-implemented.

Both methods:

  • Guard against null identifiers
  • Use appropriate @Cacheable with unless = "#result == null"
  • getDefaultVersionByIdentifier queries by defaultVersion=true
  • getLatestVersionByIdentifier sorts by version components descending

Based on learnings, this aligns with the design where all existing nets have a version assigned.


630-638: LGTM! Default version reassignment on delete is correct.

When the deleted version was the default, the code correctly:

  1. Fetches the latest remaining version (after cache eviction)
  2. Makes it the new default
  3. Persists the change

This aligns with the test testVersionActiveOnDelete.

Comment on lines +246 to 280
private PetriNet checkAndHandleProcessVersion(PetriNet newProcess, VersionType releaseType) {
PetriNet processToMakeNonDefault = null;

if (newProcess.getVersion() != null && self.getPetriNet(newProcess.getIdentifier(), newProcess.getVersion()) != null) {
throw new IllegalArgumentException("A process [%s] with such version [%s] already exists"
.formatted(newProcess.getIdentifier(), newProcess.getVersion()));
}
PetriNet existingLatestProcess = self.getLatestVersionByIdentifier(newProcess.getIdentifier());
boolean makeNonDefaultCurrentVersion = true;
if (existingLatestProcess == null && newProcess.getVersion() == null) {
newProcess.setVersion(new Version());
} else {
if (newProcess.getVersion() == null) {
newProcess.setVersion(existingLatestProcess.getVersion().clone());
newProcess.incrementVersion(releaseType);
} else if (existingLatestProcess != null && newProcess.getVersion().isLowerThan(existingLatestProcess.getVersion())) {
makeNonDefaultCurrentVersion = false;
}
if (makeNonDefaultCurrentVersion && existingLatestProcess != null && existingLatestProcess.isDefaultVersion()) {
existingLatestProcess.makeNonDefault();
processToMakeNonDefault = existingLatestProcess;
} else if (makeNonDefaultCurrentVersion) {
PetriNet existingActiveProcess = self.getDefaultVersionByIdentifier(newProcess.getIdentifier());
if (existingActiveProcess != null) {
existingActiveProcess.makeNonDefault();
processToMakeNonDefault = existingActiveProcess;
}
}
}
if (makeNonDefaultCurrentVersion) {
newProcess.makeDefault();
}

return processToMakeNonDefault;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Complex version handling logic - consider adding inline comments.

The checkAndHandleProcessVersion method handles multiple scenarios correctly, but the conditional logic (especially lines 261-273) is dense. The Javadoc is helpful, but inline comments within the method body would improve maintainability.

Key scenarios handled:

  1. First import (no existing version) → initialize to 1.0.0
  2. Version not specified → increment from latest
  3. Version specified but lower than latest → don't make default
  4. Latest is currently default → make it non-default
  5. Latest is not default → find and update current default
🤖 Prompt for AI Agents
In
@application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
around lines 246 - 280, The conditional branches in checkAndHandleProcessVersion
are dense and need brief inline comments to explain each scenario; add short
comments near the blocks that handle: (a) initial import when
existingLatestProcess==null and newProcess.getVersion()==null (initialize new
Version), (b) version auto-increment when newProcess.getVersion()==null (clone
and increment from existingLatestProcess), (c) branch where a provided
newProcess.getVersion() is lower than existingLatestProcess.getVersion() (set
makeNonDefaultCurrentVersion=false), and (d) the two paths that demote the
current default (when existingLatestProcess.isDefaultVersion() vs when falling
back to self.getDefaultVersionByIdentifier) and finally the
newProcess.makeDefault() call; place comments adjacent to variables/conditions
like newProcess, existingLatestProcess, makeNonDefaultCurrentVersion, and
processToMakeNonDefault so future readers can quickly map each conditional to
the scenario described in the PR comment.

Comment on lines +151 to +217
void testVersionsOnImport() {
ImportPetriNetEventOutcome outcome = importProcess(VERSION_PROCESS_FILE_FORMAT.formatted("2"), superCreator.loggedSuper)
PetriNet petriNetV2 = outcome.getNet()
assert petriNetV2 != null
assert petriNetV2.defaultVersion
Version version = new Version()
version.setMajor(2)
assert petriNetV2.getVersion() == version
Thread.sleep(5000)
Optional<ElasticPetriNet> elasticPetriNetV2Optional = elasticPetriNetRepository.findById(petriNetV2.stringId)
assert elasticPetriNetV2Optional.isPresent()
assert elasticPetriNetV2Optional.get().isDefaultVersion()

outcome = importProcess(VERSION_PROCESS_FILE_FORMAT.formatted("4"), superCreator.loggedSuper)
PetriNet petriNetV4 = outcome.getNet()
assert petriNetV4 != null
assert !petriNetService.get(petriNetV2.getObjectId()).isDefaultVersion()
assert petriNetV4.isDefaultVersion()
version = new Version()
version.setMajor(4)
assert petriNetV4.getVersion() == version
Thread.sleep(5000)
elasticPetriNetV2Optional = elasticPetriNetRepository.findById(petriNetV2.stringId)
assert !elasticPetriNetV2Optional.get().isDefaultVersion()
Optional<ElasticPetriNet> elasticPetriNetV4Optional = elasticPetriNetRepository.findById(petriNetV4.stringId)
assert elasticPetriNetV4Optional.isPresent()
assert elasticPetriNetV4Optional.get().isDefaultVersion()

outcome = importProcess(VERSION_PROCESS_FILE_FORMAT.formatted("1"), superCreator.loggedSuper)
PetriNet petriNetV1 = outcome.getNet()
assert petriNetV1 != null
assert petriNetService.get(petriNetV4.getObjectId()).isDefaultVersion()
assert !petriNetV1.isDefaultVersion()
version = new Version()
version.setMajor(1)
assert petriNetV1.getVersion() == version

assertThrows(IllegalArgumentException.class, {
// cannot import already existing version
importProcess(VERSION_PROCESS_FILE_FORMAT.formatted("2"), superCreator.loggedSuper)
})

petriNetV2.makeDefault()
petriNetV2 = petriNetService.save(petriNetV2).get()
assert petriNetV2.defaultVersion
petriNetV4.makeNonDefault()
petriNetV4 = petriNetService.save(petriNetV4).get()
assert !petriNetV4.defaultVersion

outcome = importProcess(VERSION_PROCESS_FILE_FORMAT.formatted("5"), superCreator.loggedSuper)
PetriNet petriNetV5 = outcome.getNet()
assert petriNetV5 != null
assert !petriNetService.get(petriNetV2.getObjectId()).defaultVersion
assert !petriNetService.get(petriNetV4.getObjectId()).defaultVersion
assert petriNetV5.defaultVersion
version = new Version()
version.setMajor(5)
assert petriNetV5.getVersion() == version
Thread.sleep(5000)
elasticPetriNetV2Optional = elasticPetriNetRepository.findById(petriNetV2.stringId)
assert !elasticPetriNetV2Optional.get().defaultVersion
elasticPetriNetV4Optional = elasticPetriNetRepository.findById(petriNetV4.stringId)
assert !elasticPetriNetV4Optional.get().defaultVersion
Optional<ElasticPetriNet> elasticPetriNetV5Optional = elasticPetriNetRepository.findById(petriNetV5.stringId)
assert elasticPetriNetV5Optional.isPresent()
assert elasticPetriNetV5Optional.get().defaultVersion
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Good coverage for version import semantics, but Thread.sleep(5000) is fragile.

The test comprehensively covers the default version behavior during imports. However, the multiple Thread.sleep(5000) calls (lines 159, 172, 209) make this test slow (~15 seconds of sleep) and potentially flaky if Elasticsearch indexing takes longer than expected.

Consider using an await/polling pattern or a test utility that waits for the expected Elasticsearch state.

🤖 Prompt for AI Agents
In
@application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovy
around lines 151 - 217, The test testVersionsOnImport uses three
Thread.sleep(5000) calls which are slow and flaky; replace them with a
polling/await strategy that waits until the expected ElasticPetriNet state is
reached (use elasticPetriNetRepository.findById(petriNetX.stringId) and assert
conditions inside the waiter). Implement or reuse a helper like
waitForCondition/awaitility to poll until the Optional is present and its
isDefaultVersion()/defaultVersion matches the expected boolean (with a
reasonable timeout), and swap each Thread.sleep(5000) with calls to that helper;
reference the testVersionsOnImport method and the
elasticPetriNetRepository.findById checks to locate where to change.

Comment on lines +76 to +79
@Getter
@Setter
@Indexed
private boolean defaultVersion;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for defaultVersion usage across the codebase
echo "=== Searching for defaultVersion usage ==="
rg -n 'defaultVersion' --type java -B2 -A2

echo -e "\n=== Searching for version management methods ==="
rg -n 'setDefault|makeDefault|getDefault|Default.*Version' --type java | head -50

echo -e "\n=== Searching for version-related services/repositories ==="
fd -e java -path '*service*' -o -path '*repository*' | head -20

Repository: netgrif/application-engine

Length of output: 16339


🏁 Script executed:

#!/bin/bash
echo "=== Searching for getDefaultVersionByIdentifier implementation ==="
rg -n 'getDefaultVersionByIdentifier' --type java -B5 -A10

echo -e "\n=== Searching for PetriNetService class ==="
fd -name 'PetriNetService.java' | head -5

echo -e "\n=== Searching for methods that update defaultVersion ==="
rg -n 'setDefaultVersion|\.defaultVersion\s*=' --type java -B3 -A3

Repository: netgrif/application-engine

Length of output: 46164


🏁 Script executed:

#!/bin/bash
echo "=== Searching for MongoDB unique index configuration ==="
rg -n '@Indexed|@Document|unique' --type java application-engine/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/PetriNet.java

echo -e "\n=== Checking PetriNetService import context ==="
fd -name 'PetriNetService.java' -exec head -30 {} \;

echo -e "\n=== Searching for validation or constraints on defaultVersion ==="
rg -n 'defaultVersion.*unique|unique.*defaultVersion|Constraint|@[A-Z].*unique' --type java

Repository: netgrif/application-engine

Length of output: 9419


🏁 Script executed:

#!/bin/bash
echo "=== Finding PetriNet.java file ==="
find . -name 'PetriNet.java' -type f | head -10

echo -e "\n=== Search for PetriNet class with @Document annotation ==="
rg -l 'class PetriNet' --type java | head -5

echo -e "\n=== Looking for transaction management in version updates ==="
rg '@Transactional|makeDefault|makeNonDefault' --type java -B2 -A5 | head -100

Repository: netgrif/application-engine

Length of output: 15148


Consider adding a database-level unique constraint on (identifier, defaultVersion=true) to prevent concurrent race conditions.

While the service layer enforces single default through @Transactional methods (e.g., importPetriNet ensures the old default is marked non-default before the new one becomes default), there is no database-level constraint preventing multiple versions from being marked as default if updates bypass the service logic or occur concurrently. Adding a unique compound index would provide additional safety without impacting normal operations.

🤖 Prompt for AI Agents
In
@nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/PetriNet.java
around lines 76 - 79, Add a DB-level unique compound index to the PetriNet
entity to ensure only one default per identifier: update the PetriNet class
(where the field defaultVersion is declared) to declare a unique compound index
on (identifier, defaultVersion) and prefer a partial/filtered unique index where
defaultVersion is true (so only one document with defaultVersion==true per
identifier is allowed). Use the domain's index annotation mechanism (e.g.,
@CompoundIndex with def = "{'identifier': 1, 'defaultVersion': 1}", unique =
true and a partialFilter/partialExpression for {'defaultVersion': true} if
supported) or add an equivalent DB migration to create the unique partial index
at the database level.

Comment on lines +441 to +447
public void makeDefault() {
this.setDefaultVersion(true);
}

public void makeNonDefault() {
this.setDefaultVersion(false);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider restricting access or adding validation to default version methods.

The makeDefault() and makeNonDefault() methods are public but don't coordinate with any service-layer logic to ensure only one version per identifier is marked as default. This could lead to inconsistent state if called directly.

Consider:

  1. Making these methods package-private and routing calls through a service that enforces the single-default constraint
  2. Adding validation within these methods to clear other defaults

Based on learnings, PetriNet version management is typically handled by service-layer processes. These domain methods should align with that pattern.

🤖 Prompt for AI Agents
In
@nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/PetriNet.java
around lines 441 - 447, The public mutators makeDefault() and makeNonDefault()
on PetriNet currently allow callers to mark a version default without
coordinating with other versions; change their visibility to package-private
(remove public) and/or add validation logic inside makeDefault() to delegate to
or call a service (e.g., a VersionManagementService) that enforces the “single
default per identifier” rule, or have makeDefault() clear other defaults for the
same identifier by invoking that service; ensure references to
setDefaultVersion(boolean) remain internal and that all external code paths
route through the service-layer API that enforces the constraint.

# Conflicts:
#	application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
@coderabbitai coderabbitai bot added the Medium label Jan 9, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java (1)

20-23: Update stale Javadoc and add backward-compatible binding for renamed property.

The Javadoc at lines 20-23 still mentions "namespace cache sizes" despite the property being renamed to globalFunctionsCacheSize. More importantly, existing deployments using netgrif.engine.runner.field-runner.namespace-cache-size will fail to bind since there is no alias setter. Add a deprecated alias to maintain backward compatibility for one release cycle:

Proposed fix
     /**
-     * Configuration for the field runner, including action, function, and namespace cache sizes.
+     * Configuration for the field runner, including action and function cache sizes.
      */
     private FieldRunnerProperties fieldRunner = new FieldRunnerProperties();
@@
         /**
          * The size of the cache used for managing global Petri net functions.
          */
         private int globalFunctionsCacheSize = 500;
 
+        /**
+         * @deprecated Use {@link #setGlobalFunctionsCacheSize(int)} instead.
+         *             Kept for backward compatibility with configs using
+         *             {@code netgrif.engine.runner.field-runner.namespace-cache-size}.
+         */
+        @Deprecated
+        public void setNamespaceCacheSize(int namespaceCacheSize) {
+            this.globalFunctionsCacheSize = namespaceCacheSize;
+        }
🤖 Fix all issues with AI agents
In
@application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java:
- Around line 49-52: Update the Javadoc on the CacheConfigurationProperties
field globalFunctions to remove redundancy; replace the current sentence
"Default cache name for caching global functions of PetriNet global scoped
functions." with a concise phrasing such as "Default cache name for caching
PetriNet global functions." or "Default cache name for caching global-scoped
functions of PetriNets." Keep the field name globalFunctions unchanged.

In
@application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java:
- Around line 62-67: Add bean validation to the functionCachingPageSize field by
annotating the field functionCachingPageSize with @Min(1) so values less than 1
are rejected; also add the corresponding import for
javax.validation.constraints.Min (or jakarta.validation.constraints.Min
consistent with the project) so the annotation compiles. Ensure the annotation
is placed directly above the private int functionCachingPageSize = 500;
declaration.

In
@application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java:
- Line 4: Replace the wildcard import "import
com.netgrif.application.engine.objects.petrinet.domain.*;" with explicit imports
for every class or interface referenced by IPetriNetService (e.g., PetriNet,
PetriNetVersion, Place, Transition, Arc, Flow, Marking — or the actual types
used in this interface); locate usages of domain types in the IPetriNetService
method signatures and add individual import statements for each to restore
clarity and avoid type ambiguity.

In
@application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java:
- Around line 70-74: Fetch the Petri net version into a local variable from
petriNetService.getDefaultVersionByIdentifier(petriNetIdentifier), check if that
value is null and if so throw the same kind of not-found/runtime exception used
elsewhere (include the petriNetIdentifier in the message), otherwise remove the
entry from globalFunctionsCache and call cachePetriNetFunctions with the
non-null value; update reloadCachedFunctions to perform the null-check before
calling cachePetriNetFunctions and mirror the null-safety pattern used in other
services.

In @docs/process/functions.md:
- Line 7: Edit the sentence "Petriflow functions provide opportunity to declare
your own functions without any changes in applications code." and remove the
trailing space at the end of the line so the Markdown lint error is resolved;
ensure no other trailing whitespace exists on that line.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 749db25 and 94724c5.

📒 Files selected for processing (10)
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java
  • application-engine/src/main/java/com/netgrif/application/engine/importer/service/FunctionFactory.java
  • application-engine/src/main/java/com/netgrif/application/engine/importer/service/FunctionValidator.java
  • application-engine/src/main/java/com/netgrif/application/engine/importer/service/IFunctionValidator.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/repositories/PetriNetRepository.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
  • docs/process/functions.md
🧰 Additional context used
🧠 Learnings (9)
📓 Common learnings
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 363
File: application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java:66-69
Timestamp: 2026-01-08T08:37:58.317Z
Learning: In the CacheConfigurationProperties class, the cache property for namespace/global functions is named `globalFunctions`, not `namespaceFunctions`. This property was renamed as part of the refactoring from namespace-scoped to global-scoped function caching.
📚 Learning: 2026-01-08T08:37:58.317Z
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 363
File: application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java:66-69
Timestamp: 2026-01-08T08:37:58.317Z
Learning: In the CacheConfigurationProperties class, the cache property for namespace/global functions is named `globalFunctions`, not `namespaceFunctions`. This property was renamed as part of the refactoring from namespace-scoped to global-scoped function caching.

Applied to files:

  • application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java
📚 Learning: 2025-07-31T23:40:46.499Z
Learnt from: tuplle
Repo: netgrif/application-engine PR: 334
File: application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java:204-214
Timestamp: 2025-07-31T23:40:46.499Z
Learning: In the PetriNetService.importPetriNet method, existingNet.getVersion() cannot be null because all existing nets in the system were deployed through processes that ensure every net always has a version assigned.

Applied to files:

  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
📚 Learning: 2025-12-19T09:06:38.316Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 398
File: application-engine/src/main/java/com/netgrif/application/engine/importer/service/Importer.java:57-58
Timestamp: 2025-12-19T09:06:38.316Z
Learning: In the file `application-engine/src/main/java/com/netgrif/application/engine/importer/service/Importer.java`, the `definition` attribute of the `Action` class (from `com.netgrif.application.engine.objects.petrinet.domain.dataset.logic.action.Action`) is never null, so null checks for `action.getDefinition()` are not needed.

Applied to files:

  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
📚 Learning: 2025-12-19T07:55:13.427Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 400
File: application-engine/src/main/java/com/netgrif/application/engine/workflow/web/AbstractTaskController.java:239-247
Timestamp: 2025-12-19T07:55:13.427Z
Learning: In the netgrif/application-engine project, the `taskService.findOne()` method (from ITaskService) never returns null. When a task is not found, it handles the error through other means (such as throwing an exception) rather than returning null, so null checks after `findOne()` calls are unnecessary.

Applied to files:

  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
📚 Learning: 2025-11-14T10:22:01.634Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 383
File: application-engine/src/main/java/com/netgrif/application/engine/startup/ApplicationRunnerOrderResolver.java:43-43
Timestamp: 2025-11-14T10:22:01.634Z
Learning: For the netgrif/application-engine repository, avoid flagging trivial or nitpick-level issues such as redundant null checks, minor code style improvements, or obvious simplifications that don't affect functionality or introduce bugs. Focus review comments on substantive issues like logic errors, security concerns, performance problems, or breaking changes.

Applied to files:

  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
📚 Learning: 2025-09-29T10:31:57.325Z
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 362
File: application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/ProcessRoleService.java:513-529
Timestamp: 2025-09-29T10:31:57.325Z
Learning: PetriNet.getStringId() returns a simple ObjectId string representation (_id.toString()), not a composite Netgrif ID format, so new ObjectId(petriNetId) works correctly when petriNetId comes from PetriNet.getStringId().

Applied to files:

  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
📚 Learning: 2025-07-29T17:19:18.300Z
Learnt from: tuplle
Repo: netgrif/application-engine PR: 331
File: application-engine/src/main/java/com/netgrif/application/engine/elastic/service/ElasticPetriNetService.java:45-46
Timestamp: 2025-07-29T17:19:18.300Z
Learning: In ElasticPetriNetService class, petriNetService is properly initialized using Lazy setter injection rather than constructor injection. This pattern with Lazy Autowired setter methods is commonly used in Spring to resolve circular dependencies and is a valid alternative to constructor injection.

Applied to files:

  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
📚 Learning: 2025-12-12T12:36:06.651Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 397
File: application-engine/src/main/resources/petriNets/engine-processes/org_group.xml:36-39
Timestamp: 2025-12-12T12:36:06.651Z
Learning: In the org_group.xml file (application-engine/src/main/resources/petriNets/engine-processes/org_group.xml), variable names in action blocks like userField and user_selection should be kept as-is even when the field type changes from "user" to "actor".

Applied to files:

  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
🧬 Code graph analysis (3)
application-engine/src/main/java/com/netgrif/application/engine/importer/service/FunctionValidator.java (1)
application-engine/src/main/java/com/netgrif/application/engine/importer/service/FunctionFactory.java (1)
  • Slf4j (10-28)
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (1)
nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/ActorTransformer.java (1)
  • ActorTransformer (10-131)
application-engine/src/main/java/com/netgrif/application/engine/importer/service/FunctionFactory.java (1)
application-engine/src/main/java/com/netgrif/application/engine/importer/service/FunctionValidator.java (1)
  • Component (8-29)
🪛 GitHub Actions: Pull Request
application-engine/src/main/java/com/netgrif/application/engine/importer/service/FunctionValidator.java

[error] 26-26: cannot find symbol: GLOBAL in class com.netgrif.application.engine.objects.importer.model.Scope

🪛 markdownlint-cli2 (0.18.1)
docs/process/functions.md

7-7: Trailing spaces
Expected: 0 or 2; Actual: 1

(MD009, no-trailing-spaces)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
🔇 Additional comments (25)
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java (3)

160-166: LGTM! New method is well-documented.

The new getLatestVersionByIdentifier method provides clear semantics distinguishing between "default" and "latest" versions, which aligns with the PR objectives.


176-181: LGTM! New method is well-documented.

The getAllDefault method provides a clear way to retrieve all default version PetriNets with pagination support.


153-158: Ensure all implementations and call sites have been updated to use getDefaultVersionByIdentifier.

The method getNewestVersionByIdentifier has been renamed to getDefaultVersionByIdentifier, which is a breaking API change. Verify that all implementations and call sites across the codebase have been updated accordingly.

application-engine/src/main/java/com/netgrif/application/engine/importer/service/IFunctionValidator.java (1)

1-15: LGTM! Interface is well-designed and documented.

The new IFunctionValidator interface is clean and follows good practices:

  • Extends the existing IModelValidator for consistency
  • Javadoc clearly documents the mutation behavior of checkDeprecatedAttributes
  • Single responsibility principle is maintained
application-engine/src/main/java/com/netgrif/application/engine/importer/service/FunctionFactory.java (1)

15-18: LGTM! Proper dependency injection and validation flow.

The addition of IFunctionValidator via constructor injection follows best practices, and calling checkDeprecatedAttributes before processing the function ensures deprecated attributes are normalized early in the pipeline.

Note: This code depends on FunctionValidator being fixed (see compilation error in that file).

application-engine/src/main/java/com/netgrif/application/engine/importer/service/FunctionValidator.java (1)

23-28: Verify: Scope constant usage in deprecated attribute check

The code references Scope.GLOBAL at line 26, but this constant cannot be verified in the codebase. The Scope class is generated from an external XSD schema (https://petriflow.org/petriflow.schema.xsd) via JAXB, making its available constants inaccessible for direct verification.

Confirm what constants are available in the generated Scope enum and whether Scope.GLOBAL exists. If not, determine the correct constant name to use when replacing the deprecated NAMESPACE scope. Note that FunctionScope (used elsewhere in the codebase) does have a GLOBAL constant, but that is a different class than Scope.

application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/repositories/PetriNetRepository.java (2)

36-44: LGTM!

The repository method declaration follows Spring Data MongoDB conventions correctly, and the Javadoc clearly describes the filtering behavior.


82-89: LGTM!

The repository method correctly uses Spring Data's derived query naming convention for boolean properties. The Javadoc appropriately describes the method's purpose.

application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java (2)

30-37: LGTM!

The split of the single petriNetNewest cache into two separate caches (petriNetDefault and petriNetLatest) clearly supports the new version management semantics introduced in this PR.


67-68: LGTM!

The getAllCaches() method correctly includes all the new cache names (petriNetDefault, petriNetLatest, globalFunctions) and maintains order using LinkedHashSet.

application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (6)

18-20: LGTM!

The pagination imports are appropriately added to support the new cacheAllPetriNetFunctions() method.


34-34: LGTM!

The renaming from namespaceFunctionsCache to globalFunctionsCache correctly reflects the scope change. Thread-safety is ensured with Collections.synchronizedMap, and cache bounds are enforced with MaxSizeHashMap.

Also applies to: 40-42


46-50: LGTM!

The lazy setter injection pattern is appropriate for resolving circular dependencies. This pattern is consistently used in the codebase.


58-67: LGTM!

The method correctly filters for global-scoped functions and stores them in globalFunctionsCache keyed by PetriNet identifier. The cleanup of empty entries is appropriate.


103-123: LGTM with good error handling.

The pagination logic is correct:

  • isEmpty() check on line 108 prevents processing empty pages
  • hasNext() check on line 117 prevents unnecessary page fetches
  • Per-PetriNet try-catch on lines 110-114 ensures one failing PetriNet doesn't stop the entire batch

The defensive error handling is particularly valuable for preventing cascading failures during bulk cache population.


163-165: LGTM!

The method renames from "namespace" to "global" are consistent with the scope refactoring. Cache reinitialization maintains thread-safety with synchronized maps.

Also applies to: 173-175

application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (9)

142-143: LGTM!

Cache eviction logic correctly updated to handle the new petriNetDefault and petriNetLatest caches, replacing the single petriNetNewest cache.

Also applies to: 150-151


181-224: LGTM!

The import flow correctly:

  1. Validates and handles version semantics via checkAndHandleProcessVersion
  2. Saves the demoted process before saving the new process (lines 214-217)
  3. Caches functions after successful save
  4. Maintains transactional integrity with @Transactional

The order of operations ensures database consistency during version transitions.


283-299: LGTM!

The refactoring of save() to delegate to doSaveInternal() enables reuse in the import flow. Key improvements:

  • Indexing errors don't fail the save operation (lines 292-296)
  • Proper SLF4J placeholder usage in logging (line 295)
  • protected final modifier prevents subclass override while allowing internal use

314-324: LGTM!

The added null guards (lines 315-317) prevent potential issues and align with the cache configuration's unless = "#result == null" condition.


339-365: LGTM!

Both methods are well-implemented with:

  • Appropriate caching configuration matching the new cache semantics
  • Null-safety for identifier parameter
  • Efficient pagination (limit 1) for single-result queries
  • Correct sorting for latest version (DESC on major, minor, patch)

The return of null when not found is consistent with getPetriNet(String, Version).


417-422: LGTM!

The getAllDefault() method follows the established pattern of other pagination methods in this service, including proper arc initialization.


503-503: LGTM!

The updated reference retrieval correctly uses getDefaultVersionByIdentifier when no version is specified, aligning with the new default/latest version semantics.


637-645: LGTM!

The default version promotion logic correctly handles deletion:

  1. Only triggers when deleting a default version (line 637)
  2. Promotes the remaining latest version to default (line 642)
  3. Null-safe check prevents errors when no other versions exist (line 639)

The order of operations ensures the deleted process is not considered for promotion (deletion at line 634 occurs before fetching latest at line 638).


226-280: Version management logic is correct; the distinction between default and latest versions is intentional.

The code correctly handles the case where the default version differs from the latest version:

  1. Dual version queries (lines 253, 268): The system maintains separate "latest" and "default" version concepts. getLatestVersionByIdentifier() retrieves the highest version number; getDefaultVersionByIdentifier() retrieves the marked-default version.

  2. Default demotion logic (lines 264–273) handles two scenarios:

    • If latest is default (line 264): demote it
    • If latest is NOT default (line 267): query for and demote any other default version

This is necessary because a non-latest version can legitimately remain default. For example: importing v2.0 (default), then v3.0 (becomes default), then v2.5 (stays non-default because 3.0 is latest).

  1. Deletion recovery (lines 637–642): When the default version is deleted, the system auto-promotes the latest to default, ensuring consistency.

The complex nesting is justified—the code correctly implements support for independent default/latest version management.

Comment on lines 62 to 67
/**
* The size of pages used for caching functions when processing large sets of data.
* This property determines the maximum number of functions to include in a single page during caching operations.
* Default value is 500.
*/
private int functionCachingPageSize = 500;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the property class to see if there are validation annotations
cat -n application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java | head -100

Repository: netgrif/application-engine

Length of output: 3681


🏁 Script executed:

# Search for all usages of functionCachingPageSize in the codebase
rg "functionCachingPageSize" -A 3 -B 3

Repository: netgrif/application-engine

Length of output: 1171


🏁 Script executed:

# Check if there are any validation annotations on the property
rg "functionCachingPageSize" --type java -A 5 | grep -E "@|functionCachingPageSize"

Repository: netgrif/application-engine

Length of output: 244


🏁 Script executed:

# Look for the caching implementation that uses this property
rg "caching|cache" --type java -l | head -20

Repository: netgrif/application-engine

Length of output: 2112


🏁 Script executed:

# Find where functionCachingPageSize is actually used
rg "functionCachingPageSize" --type java -B 5 -A 10

Repository: netgrif/application-engine

Length of output: 2625


🏁 Script executed:

# Examine the FunctionsCacheRunner to see how this property is used
cat -n application-engine/src/main/java/com/netgrif/application/engine/startup/runner/FunctionsCacheRunner.java

Repository: netgrif/application-engine

Length of output: 1413


🏁 Script executed:

# Check if there are any validation annotations on similar cache properties in the codebase
rg "@Min|@Max|@Positive|@NotNull" --type java -B 2 -A 2 | grep -A 5 -B 5 "CacheSize\|PageSize"

Repository: netgrif/application-engine

Length of output: 52


🏁 Script executed:

# Search for any validation in configuration properties
rg "CacheSize\|PageSize" --type java -A 3 -B 3 | grep -E "@|CacheSize|PageSize"

Repository: netgrif/application-engine

Length of output: 52


🏁 Script executed:

# Search for where functionCachingPageSize might be injected or accessed
rg "functionCachingPageSize\|getFieldRunner" --type java -B 3 -A 3

Repository: netgrif/application-engine

Length of output: 52


🏁 Script executed:

# Look for pageable or pagination logic in the caching services
rg "PageRequest\|Pageable" --type java -l | grep -i cache

Repository: netgrif/application-engine

Length of output: 52


🏁 Script executed:

# Check FieldActionsCacheService for pagination usage
cat -n application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java | head -150

Repository: netgrif/application-engine

Length of output: 7378


🏁 Script executed:

# Check if there are any other usages of functionCachingPageSize beyond what we found
rg "getFunctionCachingPageSize" --type java -B 5 -A 5

Repository: netgrif/application-engine

Length of output: 1650


🏁 Script executed:

# Look for all setter/getter patterns related to this property
rg "FunctionCachingPageSize" --type java -i

Repository: netgrif/application-engine

Length of output: 443


🏁 Script executed:

# Check what other cache size properties exist and if any have validation
cat -n application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java | head -100

Repository: netgrif/application-engine

Length of output: 2804


🏁 Script executed:

# Check FieldActionsCacheProperties to see if there's any validation pattern there
cat -n application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/FieldActionsCacheProperties.java

Repository: netgrif/application-engine

Length of output: 692


🏁 Script executed:

# Let's verify Spring Data's PageRequest behavior with a quick search
# Check if there are any tests or documentation about PageRequest.of() behavior
fd "PageRequest" --type f | head -5

Repository: netgrif/application-engine

Length of output: 312


🏁 Script executed:

# Look for any error handling or validation patterns in the codebase for similar properties
rg "PageRequest\|IllegalArgumentException.*size\|size.*0" --type java -B 3 -A 3 | head -40

Repository: netgrif/application-engine

Length of output: 52


🏁 Script executed:

# Check if there are any validation bean imports in RunnerConfigurationProperties
head -20 application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java

Repository: netgrif/application-engine

Length of output: 679


🏁 Script executed:

# Search for validation annotations used elsewhere in configuration properties
fd "ConfigurationProperties" --type f -x grep -l "@Min\|@Max\|@Positive\|@NotNull" {} \;

Repository: netgrif/application-engine

Length of output: 193


🏁 Script executed:

# Check the DataConfigurationProperties file that was found with validation annotations
cat -n application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/DataConfigurationProperties.java | head -100

Repository: netgrif/application-engine

Length of output: 4156


🏁 Script executed:

# Search for @Min, @Max, @Positive annotations in the entire configuration properties
find application-engine/src/main/java/com/netgrif/application/engine/configuration/properties -name "*.java" -exec grep -l "@Min\|@Max\|@Positive" {} \;

Repository: netgrif/application-engine

Length of output: 191


🏁 Script executed:

# Look at the actual pagination logic more carefully - check the complete cacheAllPetriNetFunctions method
sed -n '103,123p' application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java

Repository: netgrif/application-engine

Length of output: 810


🏁 Script executed:

# Check if MaxSizeHashMap that's used for cache size validation has any constraints
rg "MaxSizeHashMap" --type java -B 5 -A 15 | head -60

Repository: netgrif/application-engine

Length of output: 8913


Add @Min(1) validation to functionCachingPageSize.

The property lacks explicit validation despite similar cache size properties in the codebase. While Spring Data's PageRequest.of() validates the size internally and rejects values ≤ 0, adding @Min(1) annotation aligns with validation patterns used elsewhere (e.g., DataConfigurationProperties) and provides explicit contract documentation.

🤖 Prompt for AI Agents
In
@application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java
around lines 62 - 67, Add bean validation to the functionCachingPageSize field
by annotating the field functionCachingPageSize with @Min(1) so values less than
1 are rejected; also add the corresponding import for
javax.validation.constraints.Min (or jakarta.validation.constraints.Min
consistent with the project) so the annotation compiles. Ensure the annotation
is placed directly above the private int functionCachingPageSize = 500;
declaration.

import com.netgrif.application.engine.objects.petrinet.domain.PetriNetSearch;
import com.netgrif.application.engine.objects.petrinet.domain.Transition;
import com.netgrif.application.engine.objects.petrinet.domain.VersionType;
import com.netgrif.application.engine.objects.petrinet.domain.*;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider using explicit imports instead of wildcard.

The change from explicit imports to a wildcard import reduces code clarity and may introduce ambiguity if multiple types with the same name exist in the package.

♻️ Revert to explicit imports
-import com.netgrif.application.engine.objects.petrinet.domain.*;
+import com.netgrif.application.engine.objects.petrinet.domain.PetriNet;
+import com.netgrif.application.engine.objects.petrinet.domain.PetriNetSearch;
+import com.netgrif.application.engine.objects.petrinet.domain.Transition;
+import com.netgrif.application.engine.objects.petrinet.domain.VersionType;

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
@application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java
at line 4, Replace the wildcard import "import
com.netgrif.application.engine.objects.petrinet.domain.*;" with explicit imports
for every class or interface referenced by IPetriNetService (e.g., PetriNet,
PetriNetVersion, Place, Transition, Arc, Flow, Marking — or the actual types
used in this interface); locate usages of domain types in the IPetriNetService
method signatures and add individual import statements for each to restore
clarity and avoid type ambiguity.


## Overview

Petriflow functions provide opportunity to declare your own functions without any changes in applications code.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove trailing space.

Markdownlint detected a trailing space at the end of line 7. Please remove it to comply with markdown formatting standards.

🔧 Proposed fix
-Petriflow functions provide opportunity to declare your own functions without any changes in applications code. 
+Petriflow functions provide opportunity to declare your own functions without any changes in applications code.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Petriflow functions provide opportunity to declare your own functions without any changes in applications code.
Petriflow functions provide opportunity to declare your own functions without any changes in applications code.
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

7-7: Trailing spaces
Expected: 0 or 2; Actual: 1

(MD009, no-trailing-spaces)

🤖 Prompt for AI Agents
In @docs/process/functions.md at line 7, Edit the sentence "Petriflow functions
provide opportunity to declare your own functions without any changes in
applications code." and remove the trailing space at the end of the line so the
Markdown lint error is resolved; ensure no other trailing whitespace exists on
that line.

# Conflicts:
#	application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
# Conflicts:
#	application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
Moved `functionCachingPageSize` from `RunnerConfigurationProperties` to `CacheConfigurationProperties` to improve property organization and maintainability. This ensures related cache settings are grouped together for better clarity.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java (1)

49-59: Clarify the distinction between functionsCacheSize and globalFunctionsCacheSize.

With the addition of globalFunctionsCacheSize, there are now two function-related cache properties in FieldRunnerProperties:

  • functionsCacheSize (line 54) - "for managing field runner functions"
  • globalFunctionsCacheSize (line 59) - "for managing global Petri net functions"

The semantic distinction between "field runner functions" and "global Petri net functions" is unclear from the current documentation. Please enhance the Javadocs to clarify:

  • What types of functions each cache manages
  • When users should configure one vs. the other
  • How these caches interact with the broader caching infrastructure

This will help maintainers and users understand the configuration options and set appropriate values.

Based on learnings, this refactoring introduces global-scoped function caching to replace namespace-scoped caching. Consider adding this context to the documentation.

🤖 Fix all issues with AI agents
In
@application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java:
- Around line 49-52: Update the Javadoc for the CacheConfigurationProperties
field globalFunctions to remove the redundant wording; replace "Default cache
name for caching global functions of PetriNet global scoped functions." with a
simpler sentence such as "Default cache name for caching PetriNet global-scoped
functions." (keep field name globalFunctions unchanged).

In
@application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java:
- Around line 57-59: CacheConfiguration.cacheManager() currently registers the
"globalFunctions" cache as a plain ConcurrentMapCache and ignores the
FieldRunnerProperties.globalFunctionsCacheSize; change the registration to
construct the "globalFunctions" cache using GenericMapCache with a supplier that
reads fieldRunnerProperties.getGlobalFunctionsCacheSize(), mirroring how the
"functions" cache is created, so the globalFunctions cache is bounded by the
configured size (update the cacheManager() method to replace the
ConcurrentMapCache instantiation for "globalFunctions" with a GenericMapCache
supplier using fieldRunnerProperties.getGlobalFunctionsCacheSize()).

In
@application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java:
- Around line 97-112: getCachedFunctions has an NPE risk similar to
getCompiledAction because it calls functionsCache.get(...).get() without
guaranteeing the entry exists; if nativeFunctionsCache is not a Map the put is
skipped but the subsequent get() still occurs. Fix by ensuring you never call
.get() on a null Cache.Value: when iterating functions, first retrieve
Cache.Value v = functionsCache.get(id) and if v == null create the
CachedFunction via CachedFunction.build(shell, function) and insert it (use
functionsCache.putIfAbsent / put), then re-read v = functionsCache.get(id) and
only then cast and add ((CachedFunction) v.get()); alternatively use a
compute-if-absent pattern so the cached value is always present before calling
.get(). Ensure this logic is applied in getCachedFunctions (and mirror the same
null-safe pattern used in getCompiledAction).
- Around line 71-75: The reloadCachedFunctions method evicts the cache
unconditionally and then calls cachePetriNetFunctions with
petriNetService.getDefaultVersionByIdentifier(petriNetId) which may return null,
leaving the cache empty without notice; update reloadCachedFunctions to first
fetch the default version into a local variable via
petriNetService.getDefaultVersionByIdentifier(petriNetId), check for null, and
if null either skip eviction and log a warning (using the existing logger) or
evict and then log a warning that no default version exists, otherwise proceed
to call cachePetriNetFunctions with the non-null result; reference methods:
reloadCachedFunctions, getRequiredCache(properties.getGlobalFunctions()),
petriNetService.getDefaultVersionByIdentifier(...) and cachePetriNetFunctions.
- Around line 82-95: In getCompiledAction modify logic to avoid calling .get()
on a possibly null Cache.ValueWrapper: after obtaining actionsCache and
nativeActionsCache, compute/put the compiled Closure (using shell.evaluate("{->
" + action.getDefinition() + "}")) when needed as you already do, then fetch
Cache.ValueWrapper wrapper = actionsCache.get(stringId) and check for null
before calling wrapper.get(); if wrapper is null (native cache not a Map or
evicted race), return the freshly compiled Closure (or re-evaluate and put it
into actionsCache) so the method always returns a Closure without risking a
NullPointerException. Ensure you reference getCompiledAction, actionsCache,
Cache.ValueWrapper and shell.evaluate in your changes.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 94724c5 and d573cf2.

📒 Files selected for processing (3)
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 363
File: application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java:66-69
Timestamp: 2026-01-08T08:37:58.317Z
Learning: In the CacheConfigurationProperties class, the cache property for namespace/global functions is named `globalFunctions`, not `namespaceFunctions`. This property was renamed as part of the refactoring from namespace-scoped to global-scoped function caching.
📚 Learning: 2026-01-08T08:37:58.317Z
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 363
File: application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java:66-69
Timestamp: 2026-01-08T08:37:58.317Z
Learning: In the CacheConfigurationProperties class, the cache property for namespace/global functions is named `globalFunctions`, not `namespaceFunctions`. This property was renamed as part of the refactoring from namespace-scoped to global-scoped function caching.

Applied to files:

  • application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java
📚 Learning: 2025-11-14T10:22:01.634Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 383
File: application-engine/src/main/java/com/netgrif/application/engine/startup/ApplicationRunnerOrderResolver.java:43-43
Timestamp: 2025-11-14T10:22:01.634Z
Learning: For the netgrif/application-engine repository, avoid flagging trivial or nitpick-level issues such as redundant null checks, minor code style improvements, or obvious simplifications that don't affect functionality or introduce bugs. Focus review comments on substantive issues like logic errors, security concerns, performance problems, or breaking changes.

Applied to files:

  • application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java
📚 Learning: 2025-07-31T23:40:46.499Z
Learnt from: tuplle
Repo: netgrif/application-engine PR: 334
File: application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java:204-214
Timestamp: 2025-07-31T23:40:46.499Z
Learning: In the PetriNetService.importPetriNet method, existingNet.getVersion() cannot be null because all existing nets in the system were deployed through processes that ensure every net always has a version assigned.

Applied to files:

  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
📚 Learning: 2025-12-19T07:55:13.427Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 400
File: application-engine/src/main/java/com/netgrif/application/engine/workflow/web/AbstractTaskController.java:239-247
Timestamp: 2025-12-19T07:55:13.427Z
Learning: In the netgrif/application-engine project, the `taskService.findOne()` method (from ITaskService) never returns null. When a task is not found, it handles the error through other means (such as throwing an exception) rather than returning null, so null checks after `findOne()` calls are unnecessary.

Applied to files:

  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
📚 Learning: 2025-12-19T09:06:38.316Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 398
File: application-engine/src/main/java/com/netgrif/application/engine/importer/service/Importer.java:57-58
Timestamp: 2025-12-19T09:06:38.316Z
Learning: In the file `application-engine/src/main/java/com/netgrif/application/engine/importer/service/Importer.java`, the `definition` attribute of the `Action` class (from `com.netgrif.application.engine.objects.petrinet.domain.dataset.logic.action.Action`) is never null, so null checks for `action.getDefinition()` are not needed.

Applied to files:

  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
🔇 Additional comments (8)
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (5)

32-49: LGTM!

Constructor injection with lazy setter injection for IPetriNetService correctly handles the circular dependency pattern.


51-69: LGTM!

The caching logic correctly handles the global functions scope, including the null check and eviction when no functions are present.


114-134: LGTM!

Good resilience pattern with per-PetriNet error handling during bulk caching. The pagination logic correctly handles iteration and termination.


136-171: LGTM!

The function evaluation and signature validation logic correctly detects duplicate method signatures.


193-199: LGTM!

The getRequiredCache helper method provides good defensive programming by failing fast with a clear error message when a cache is not configured.

application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java (3)

74-75: LGTM!

The method correctly includes all new cache names (petriNetDefault, petriNetLatest, globalFunctions) in the returned set, maintaining insertion order via LinkedHashSet.


60-65: LGTM. The implementation is correct and well-documented. The functionCachingPageSize property is actively used in FieldActionsCacheService.cacheAllPetriNetFunctions() to paginate through PetriNets when caching their functions, and the default value of 500 is consistent with the legacy configuration.


30-37: Update the legacy application-old-dev.properties file to reflect the new cache property names.

The migration from petriNetNewest to petriNetDefault and petriNetLatest is complete in the active codebase. However, the legacy configuration file application-old-dev.properties (lines 38–40) still references the outdated nae.cache.petriNetNewest property. While this file is not actively used, update it to prevent developer confusion:

  • Replace nae.cache.petriNetNewest=petriNetNewest with the new properties: nae.cache.petriNetDefault=petriNetDefault and nae.cache.petriNetLatest=petriNetLatest
  • Alternatively, add a comment indicating the file is deprecated/for reference only.
⛔ Skipped due to learnings
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 363
File: application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java:66-69
Timestamp: 2026-01-08T08:37:58.317Z
Learning: In the CacheConfigurationProperties class, the cache property for namespace/global functions is named `globalFunctions`, not `namespaceFunctions`. This property was renamed as part of the refactoring from namespace-scoped to global-scoped function caching.
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 383
File: application-engine/src/main/java/com/netgrif/application/engine/startup/ApplicationRunnerOrderResolver.java:43-43
Timestamp: 2025-11-14T10:22:01.634Z
Learning: For the netgrif/application-engine repository, avoid flagging trivial or nitpick-level issues such as redundant null checks, minor code style improvements, or obvious simplifications that don't affect functionality or introduce bugs. Focus review comments on substantive issues like logic errors, security concerns, performance problems, or breaking changes.

Comment on lines +49 to +52
/**
* Default cache name for caching global functions of PetriNet global scoped functions.
*/
private String globalFunctions = "globalFunctions";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Clarify the documentation wording.

The phrase "caching global functions of PetriNet global scoped functions" is redundant. Consider simplifying to:

  • "Default cache name for caching PetriNet global-scoped functions." or
  • "Default cache name for caching global functions for PetriNets."
📝 Suggested documentation improvement
     /**
-     * Default cache name for caching global functions of PetriNet global scoped functions.
+     * Default cache name for caching PetriNet global-scoped functions.
      */
     private String globalFunctions = "globalFunctions";

Based on learnings, the property name globalFunctions is correct.

🤖 Prompt for AI Agents
In
@application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java
around lines 49 - 52, Update the Javadoc for the CacheConfigurationProperties
field globalFunctions to remove the redundant wording; replace "Default cache
name for caching global functions of PetriNet global scoped functions." with a
simpler sentence such as "Default cache name for caching PetriNet global-scoped
functions." (keep field name globalFunctions unchanged).

Comment on lines +57 to +59
* The size of the cache used for managing global Petri net functions.
*/
private int namespaceCacheSize = 500;
private int globalFunctionsCacheSize = 500;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for references to the old namespaceCacheSize property name

echo "=== Searching for Java references to namespaceCacheSize ==="
rg -n --type=java 'namespaceCacheSize|getNamespaceCacheSize|setNamespaceCacheSize'

echo -e "\n=== Searching for property file references ==="
rg -n --type=yaml --type=properties 'namespaceCacheSize|namespace-cache-size|namespace_cache_size'

echo -e "\n=== Searching for test file references ==="
rg -n --type=java 'namespaceCacheSize' -- '**/*.java'

echo -e "\n=== Checking for migration guides or deprecation notes ==="
rg -n -i 'migration|deprecat|namespace.*cache|namespaceCacheSize'

Repository: netgrif/application-engine

Length of output: 50486


🏁 Script executed:

#!/bin/bash
# Verify the current state of RunnerConfigurationProperties.java around lines 57-59

echo "=== Reading RunnerConfigurationProperties.java lines 50-65 ==="
sed -n '50,65p' application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java

echo -e "\n=== Checking for getter/setter methods for globalFunctionsCacheSize ==="
rg -n -A5 -B5 'globalFunctionsCacheSize' application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java

Repository: netgrif/application-engine

Length of output: 932


🏁 Script executed:

#!/bin/bash
# Search for all property definitions in CacheConfigurationProperties and FieldRunnerProperties

echo "=== Checking CacheConfigurationProperties for related properties ==="
find . -name 'CacheConfigurationProperties.java' -exec cat -n {} \;

echo -e "\n=== Searching for uses of globalFunctionsCacheSize in codebase ==="
rg -n 'globalFunctionsCacheSize' --type=java

Repository: netgrif/application-engine

Length of output: 3469


🏁 Script executed:

#!/bin/bash
# Search for usage of cache size properties in implementation code

echo "=== Searching for functionsCacheSize usage ==="
rg -n 'functionsCacheSize' --type=java | head -20

echo -e "\n=== Searching for globalFunctionsCacheSize usage outside property definition ==="
rg -n 'globalFunctionsCacheSize' --type=java

echo -e "\n=== Searching for references to FieldRunnerProperties or cache configuration ==="
rg -n 'FieldRunnerProperties|getGlobalFunctionsCacheSize|getFunctionsCacheSize' --type=java | head -30

echo -e "\n=== Checking if property is actually wired/injected ==="
rg -n 'RunnerConfigurationProperties|FieldRunnerProperties' --type=java -B2 -A2 | head -50

Repository: netgrif/application-engine

Length of output: 8490


🏁 Script executed:

#!/bin/bash
# Check where the cache sizes are actually used

echo "=== Searching for cache configuration usage in runner/function classes ==="
fd -t f -name '*.java' -path '*runner*' -o -path '*function*' | xargs rg -l 'cache|Cache' 2>/dev/null | head -10

echo -e "\n=== Check FunctionValidator for cache usage ==="
rg -n 'FunctionValidator' --type=java -A5 -B5 | head -40

echo -e "\n=== Search for any property getter usage pattern ==="
rg -n '\.get[A-Z]' application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java

Repository: netgrif/application-engine

Length of output: 5343


🏁 Script executed:

#!/bin/bash
# Verify the cache configuration wiring to understand the integration gap

echo "=== Reading CacheConfiguration.java around line 52 ==="
sed -n '20,65p' application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java

echo -e "\n=== Search for any cache setup for global functions ==="
rg -n 'globalFunctions|GlobalFunctions' --type=java -B3 -A3

echo -e "\n=== Check if there are TODOs or incomplete implementation markers ==="
rg -n 'TODO|FIXME|XXX' application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java

Repository: netgrif/application-engine

Length of output: 11223


Wire the globalFunctionsCacheSize property into cache configuration.

The globalFunctionsCacheSize property is defined in FieldRunnerProperties but not used in CacheConfiguration. The cache named "globalFunctions" is currently created as a standard ConcurrentMapCache without a size limit, unlike the "functions" cache which respects functionsCacheSize.

Update CacheConfiguration.cacheManager() to create the global functions cache using GenericMapCache with a supplier that applies fieldRunnerProperties.getGlobalFunctionsCacheSize(), similar to how the functions cache is configured (line 52).

🤖 Prompt for AI Agents
In
@application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java
around lines 57 - 59, CacheConfiguration.cacheManager() currently registers the
"globalFunctions" cache as a plain ConcurrentMapCache and ignores the
FieldRunnerProperties.globalFunctionsCacheSize; change the registration to
construct the "globalFunctions" cache using GenericMapCache with a supplier that
reads fieldRunnerProperties.getGlobalFunctionsCacheSize(), mirroring how the
"functions" cache is created, so the globalFunctions cache is bounded by the
configured size (update the cacheManager() method to replace the
ConcurrentMapCache instantiation for "globalFunctions" with a GenericMapCache
supplier using fieldRunnerProperties.getGlobalFunctionsCacheSize()).

Comment on lines +71 to +75
@Override
public void reloadCachedFunctions(String petriNetId) {
getRequiredCache(properties.getGlobalFunctions()).evictIfPresent(petriNetId);
cachePetriNetFunctions(petriNetService.getDefaultVersionByIdentifier(petriNetId));
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Cache eviction without reload if default version is missing.

If getDefaultVersionByIdentifier returns null (no default version for the identifier), the cache entry is evicted at line 73 but cachePetriNetFunctions silently returns without caching anything. This leaves the functions unavailable with no error indication.

Consider checking the result before evicting, or logging a warning:

Suggested improvement
 @Override
 public void reloadCachedFunctions(String petriNetId) {
+    PetriNet petriNet = petriNetService.getDefaultVersionByIdentifier(petriNetId);
+    if (petriNet == null) {
+        log.warn("Cannot reload cached functions: no default version found for identifier '{}'", petriNetId);
+        return;
+    }
     getRequiredCache(properties.getGlobalFunctions()).evictIfPresent(petriNetId);
-    cachePetriNetFunctions(petriNetService.getDefaultVersionByIdentifier(petriNetId));
+    cachePetriNetFunctions(petriNet);
 }
🤖 Prompt for AI Agents
In
@application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
around lines 71 - 75, The reloadCachedFunctions method evicts the cache
unconditionally and then calls cachePetriNetFunctions with
petriNetService.getDefaultVersionByIdentifier(petriNetId) which may return null,
leaving the cache empty without notice; update reloadCachedFunctions to first
fetch the default version into a local variable via
petriNetService.getDefaultVersionByIdentifier(petriNetId), check for null, and
if null either skip eviction and log a warning (using the existing logger) or
evict and then log a warning that no default version exists, otherwise proceed
to call cachePetriNetFunctions with the non-null result; reference methods:
reloadCachedFunctions, getRequiredCache(properties.getGlobalFunctions()),
petriNetService.getDefaultVersionByIdentifier(...) and cachePetriNetFunctions.

Comment on lines 82 to 95
@Override
public Closure getCompiledAction(Action action, boolean shouldRewriteCachedActions) {
String stringId = action.getId().toString();
if (shouldRewriteCachedActions || !actionsCache.containsKey(stringId)) {
Closure code = (Closure) shell.evaluate("{-> " + action.getDefinition() + "}");
actionsCache.put(stringId, code);
Cache actionsCache = getRequiredCache(CacheMapKeys.ACTIONS);
Object nativeActionsCache = actionsCache.getNativeCache();

if (nativeActionsCache instanceof Map<?, ?> map) {
if (shouldRewriteCachedActions || !map.containsKey(stringId) ) {
Closure code = (Closure) shell.evaluate("{-> " + action.getDefinition() + "}");
actionsCache.put(stringId, code);
}
}
return actionsCache.get(stringId);
return (Closure) actionsCache.get(stringId).get();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Potential NullPointerException when cache entry is missing.

actionsCache.get(stringId) returns a Cache.ValueWrapper which is null if the entry doesn't exist. Line 94 unconditionally calls .get() on it, causing an NPE if:

  1. The native cache is not a Map (the put at line 91 is skipped entirely)
  2. The entry was evicted between put and get (race condition)
Proposed fix
 @Override
 public Closure getCompiledAction(Action action, boolean shouldRewriteCachedActions) {
     String stringId = action.getId().toString();
     Cache actionsCache = getRequiredCache(CacheMapKeys.ACTIONS);
-    Object nativeActionsCache = actionsCache.getNativeCache();
-
-    if (nativeActionsCache instanceof Map<?, ?> map) {
-        if (shouldRewriteCachedActions || !map.containsKey(stringId) ) {
-            Closure code = (Closure) shell.evaluate("{-> " + action.getDefinition() + "}");
-            actionsCache.put(stringId, code);
-        }
+
+    Cache.ValueWrapper wrapper = actionsCache.get(stringId);
+    if (shouldRewriteCachedActions || wrapper == null) {
+        Closure code = (Closure) shell.evaluate("{-> " + action.getDefinition() + "}");
+        actionsCache.put(stringId, code);
+        return code;
     }
-    return (Closure) actionsCache.get(stringId).get();
+    return (Closure) wrapper.get();
 }
🤖 Prompt for AI Agents
In
@application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
around lines 82 - 95, In getCompiledAction modify logic to avoid calling .get()
on a possibly null Cache.ValueWrapper: after obtaining actionsCache and
nativeActionsCache, compute/put the compiled Closure (using shell.evaluate("{->
" + action.getDefinition() + "}")) when needed as you already do, then fetch
Cache.ValueWrapper wrapper = actionsCache.get(stringId) and check for null
before calling wrapper.get(); if wrapper is null (native cache not a Map or
evicted race), return the freshly compiled Closure (or re-evaluate and put it
into actionsCache) so the method always returns a Closure without risking a
NullPointerException. Ensure you reference getCompiledAction, actionsCache,
Cache.ValueWrapper and shell.evaluate in your changes.

Comment on lines 97 to 112
@Override
public List<CachedFunction> getCachedFunctions(List<com.netgrif.application.engine.objects.petrinet.domain.Function> functions) {
public List<CachedFunction> getCachedFunctions(List<Function> functions) {
List<CachedFunction> cachedFunctions = new ArrayList<>(functions.size());
functions.forEach(function -> {
if (!functionsCache.containsKey(function.getStringId())) {
functionsCache.put(function.getStringId(), CachedFunction.build(shell, function));
}
cachedFunctions.add(functionsCache.get(function.getStringId()));
});
Cache functionsCache = getRequiredCache(CacheMapKeys.FUNCTIONS);
Object nativeFunctionsCache = functionsCache.getNativeCache();

if (nativeFunctionsCache instanceof Map<?, ?> map) {
functions.forEach(function -> {
if (!map.containsKey(function.getStringId())) {
functionsCache.put(function.getStringId(), CachedFunction.build(shell, function));
}
cachedFunctions.add((CachedFunction) functionsCache.get(function.getStringId()).get());
});
}
return cachedFunctions;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Same NullPointerException risk as getCompiledAction.

Line 108 calls .get() on the result of functionsCache.get(), which returns null if the entry doesn't exist. If the native cache is not a Map, the put at line 106 is skipped but line 108 still attempts retrieval.

Proposed fix
 @Override
 public List<CachedFunction> getCachedFunctions(List<Function> functions) {
     List<CachedFunction> cachedFunctions = new ArrayList<>(functions.size());
     Cache functionsCache = getRequiredCache(CacheMapKeys.FUNCTIONS);
-    Object nativeFunctionsCache = functionsCache.getNativeCache();
-
-    if (nativeFunctionsCache instanceof Map<?, ?> map) {
-        functions.forEach(function -> {
-            if (!map.containsKey(function.getStringId())) {
-                functionsCache.put(function.getStringId(), CachedFunction.build(shell, function));
-            }
-            cachedFunctions.add((CachedFunction) functionsCache.get(function.getStringId()).get());
-        });
-    }
+
+    for (Function function : functions) {
+        Cache.ValueWrapper wrapper = functionsCache.get(function.getStringId());
+        CachedFunction cached;
+        if (wrapper == null) {
+            cached = CachedFunction.build(shell, function);
+            functionsCache.put(function.getStringId(), cached);
+        } else {
+            cached = (CachedFunction) wrapper.get();
+        }
+        cachedFunctions.add(cached);
+    }
     return cachedFunctions;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Override
public List<CachedFunction> getCachedFunctions(List<com.netgrif.application.engine.objects.petrinet.domain.Function> functions) {
public List<CachedFunction> getCachedFunctions(List<Function> functions) {
List<CachedFunction> cachedFunctions = new ArrayList<>(functions.size());
functions.forEach(function -> {
if (!functionsCache.containsKey(function.getStringId())) {
functionsCache.put(function.getStringId(), CachedFunction.build(shell, function));
}
cachedFunctions.add(functionsCache.get(function.getStringId()));
});
Cache functionsCache = getRequiredCache(CacheMapKeys.FUNCTIONS);
Object nativeFunctionsCache = functionsCache.getNativeCache();
if (nativeFunctionsCache instanceof Map<?, ?> map) {
functions.forEach(function -> {
if (!map.containsKey(function.getStringId())) {
functionsCache.put(function.getStringId(), CachedFunction.build(shell, function));
}
cachedFunctions.add((CachedFunction) functionsCache.get(function.getStringId()).get());
});
}
return cachedFunctions;
}
@Override
public List<CachedFunction> getCachedFunctions(List<Function> functions) {
List<CachedFunction> cachedFunctions = new ArrayList<>(functions.size());
Cache functionsCache = getRequiredCache(CacheMapKeys.FUNCTIONS);
for (Function function : functions) {
Cache.ValueWrapper wrapper = functionsCache.get(function.getStringId());
CachedFunction cached;
if (wrapper == null) {
cached = CachedFunction.build(shell, function);
functionsCache.put(function.getStringId(), cached);
} else {
cached = (CachedFunction) wrapper.get();
}
cachedFunctions.add(cached);
}
return cachedFunctions;
}
🤖 Prompt for AI Agents
In
@application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
around lines 97 - 112, getCachedFunctions has an NPE risk similar to
getCompiledAction because it calls functionsCache.get(...).get() without
guaranteeing the entry exists; if nativeFunctionsCache is not a Map the put is
skipped but the subsequent get() still occurs. Fix by ensuring you never call
.get() on a null Cache.Value: when iterating functions, first retrieve
Cache.Value v = functionsCache.get(id) and if v == null create the
CachedFunction via CachedFunction.build(shell, function) and insert it (use
functionsCache.putIfAbsent / put), then re-read v = functionsCache.get(id) and
only then cast and add ((CachedFunction) v.get()); alternatively use a
compute-if-absent pattern so the cached value is always present before calling
.get(). Ensure this logic is applied in getCachedFunctions (and mirror the same
null-safe pattern used in getCompiledAction).

Comment on lines 173 to 176
@Override
public Map<String, List<CachedFunction>> getNamespaceFunctionCache() {
return new HashMap<>(namespaceFunctionsCache);
public Map<String, List<CachedFunction>> getGlobalFunctionsCache() {
return new HashMap<>((Map<String, List<CachedFunction>>) getRequiredCache(properties.getGlobalFunctions()).getNativeCache());
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Unchecked cast assumes Map-backed cache implementation.

The cast to Map<String, List<CachedFunction>> will throw ClassCastException at runtime if the native cache is not a Map. Consider adding a defensive check:

Suggested improvement
 @Override
 public Map<String, List<CachedFunction>> getGlobalFunctionsCache() {
-    return new HashMap<>((Map<String, List<CachedFunction>>) getRequiredCache(properties.getGlobalFunctions()).getNativeCache());
+    Object nativeCache = getRequiredCache(properties.getGlobalFunctions()).getNativeCache();
+    if (nativeCache instanceof Map<?, ?> map) {
+        @SuppressWarnings("unchecked")
+        Map<String, List<CachedFunction>> typedMap = (Map<String, List<CachedFunction>>) map;
+        return new HashMap<>(typedMap);
+    }
+    return Collections.emptyMap();
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Override
public Map<String, List<CachedFunction>> getNamespaceFunctionCache() {
return new HashMap<>(namespaceFunctionsCache);
public Map<String, List<CachedFunction>> getGlobalFunctionsCache() {
return new HashMap<>((Map<String, List<CachedFunction>>) getRequiredCache(properties.getGlobalFunctions()).getNativeCache());
}
@Override
public Map<String, List<CachedFunction>> getGlobalFunctionsCache() {
Object nativeCache = getRequiredCache(properties.getGlobalFunctions()).getNativeCache();
if (nativeCache instanceof Map<?, ?> map) {
@SuppressWarnings("unchecked")
Map<String, List<CachedFunction>> typedMap = (Map<String, List<CachedFunction>>) map;
return new HashMap<>(typedMap);
}
return Collections.emptyMap();
}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
@application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java:
- Around line 18-22: The constructor GenericMapCache(String name, Class<V>
valueType, int cacheSize) only uses cacheSize as the initial capacity for the
ConcurrentHashMap backing field map, so the cache is unbounded; change the
backing implementation to enforce a maximum size (e.g., replace map with a
Caffeine Cache built with maximumSize(cacheSize) or with a synchronized
LinkedHashMap/ConcurrentLinkedHashMap using access-order eviction and
removeEldestEntry to cap size) and update usages of map in the class to interact
with the chosen cache API (put/get/remove) instead of assuming a raw
ConcurrentHashMap.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d573cf2 and 4c1cf75.

📒 Files selected for processing (2)
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-08T08:37:58.317Z
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 363
File: application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java:66-69
Timestamp: 2026-01-08T08:37:58.317Z
Learning: In the CacheConfigurationProperties class, the cache property for namespace/global functions is named `globalFunctions`, not `namespaceFunctions`. This property was renamed as part of the refactoring from namespace-scoped to global-scoped function caching.

Applied to files:

  • application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java
🧬 Code graph analysis (1)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java (1)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java (1)
  • CacheMapKeys (3-7)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
🔇 Additional comments (4)
application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)

49-89: LGTM!

The remaining Cache interface methods are implemented correctly:

  • get(Object key) properly returns null when key is absent per the Cache contract.
  • Type-safe retrieval with type.cast() will throw ClassCastException on mismatch as expected.
  • safeCast correctly validates types before storing.
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java (3)

30-53: LGTM with a minor note.

The cache configuration structure is clean. Using SimpleCacheManager with a mix of ConcurrentMapCache (for general caches) and GenericMapCache (for typed caches) is a reasonable approach.

Note: SimpleCacheManager implements InitializingBean, so Spring will automatically call afterPropertiesSet() after bean initialization to finalize the cache setup.


23-28: Good modernization from CachingConfigurerSupport to CachingConfigurer.

CachingConfigurerSupport was deprecated and removed in Spring 6.x. Implementing CachingConfigurer directly is the correct approach for Spring Boot 3.x compatibility.


44-48: No changes required.

The method getFunctionsCacheSize() is correct. The FieldRunnerProperties class contains both functionsCacheSize and globalFunctionsCacheSize as separate properties, and the call at line 47 uses the appropriate method for the functionsCacheSize property.

Comment on lines +18 to +22
public GenericMapCache(String name, Class<V> valueType, int cacheSize) {
this.name = name;
this.valueType = valueType;
this.map = new ConcurrentHashMap<>(cacheSize);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Cache is unbounded despite cacheSize parameter name.

The cacheSize parameter is only passed to ConcurrentHashMap as initial capacity, not as a maximum bound. The cache can grow indefinitely, potentially causing memory exhaustion under load. This contradicts the AI summary mentioning "bounded generic caches."

Consider using a bounded cache implementation like Caffeine or implementing LRU eviction:

Option 1: Use Caffeine for bounded caching
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;

// In constructor:
this.map = Caffeine.newBuilder()
    .maximumSize(cacheSize)
    .build();
Option 2: Use LinkedHashMap with access-order eviction
private final Map<String, V> map;

public GenericMapCache(String name, Class<V> valueType, int cacheSize) {
    this.name = name;
    this.valueType = valueType;
    this.map = Collections.synchronizedMap(new LinkedHashMap<>(cacheSize, 0.75f, true) {
        @Override
        protected boolean removeEldestEntry(Map.Entry<String, V> eldest) {
            return size() > cacheSize;
        }
    });
}
🤖 Prompt for AI Agents
In
@application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java
around lines 18 - 22, The constructor GenericMapCache(String name, Class<V>
valueType, int cacheSize) only uses cacheSize as the initial capacity for the
ConcurrentHashMap backing field map, so the cache is unbounded; change the
backing implementation to enforce a maximum size (e.g., replace map with a
Caffeine Cache built with maximumSize(cacheSize) or with a synchronized
LinkedHashMap/ConcurrentLinkedHashMap using access-order eviction and
removeEldestEntry to cap size) and update usages of map in the class to interact
with the chosen cache API (put/get/remove) instead of assuming a raw
ConcurrentHashMap.

Comment on lines +29 to +47
public <T> T get(Object key, Callable<T> loader) {
final String stringKey = String.valueOf(key);
try {
V value = map.computeIfAbsent(stringKey, cacheValue -> {
try {
T computed = loader.call();
if (computed == null) return null;
return safeCast(computed);
}
catch (Exception e) {
throw new RuntimeException(e);
}
});
return (T) value;
} catch (RuntimeException e) {
Throwable cause = (e.getCause() != null) ? e.getCause() : e;
throw new Cache.ValueRetrievalException(stringKey, loader, cause);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Null values are not cached, causing repeated loader invocations.

When loader.call() returns null (line 35), computeIfAbsent does not store the mapping. Subsequent calls with the same key will repeatedly invoke the loader, which may be expensive.

If caching null results is intentional behavior, document it. Otherwise, consider using a sentinel value or Optional<V> internally to distinguish "cached null" from "not cached."

Additionally, line 42 has an unchecked cast from V to T. While safeCast ensures the value is of type V, there's no guarantee T is assignable from V. Consider adding @SuppressWarnings("unchecked") with a comment explaining why this is safe in practice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

improvement A change that improves on an existing feature Large Medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants